响应式表单提供了一种模型驱动的方式来处理表单输入,其中的值会随时间而变化。本文会向你展示如何创建和更新基本的表单控件,接下来还会在一个表单组中使用多个控件,验证表单的值,以及创建动态表单,也就是在运行期添加或移除控件。
试试这个响应式表单的现场演练/ 下载范例。
响应式表单使用显式的、不可变的方式,管理表单在特定的时间点上的状态。对表单状态的每一次变更都会返回一个新的状态,这样可以在变化时维护模型的整体性。响应式表单是围绕 Observable
流构建的,表单的输入和值都是通过这些输入值组成的流来提供的,它可以同步访问。
响应式表单还提供了一种更直观的测试路径,因为在请求时你可以确信这些数据是一致的、可预料的。这个流的任何一个消费者都可以安全地操纵这些数据。
响应式表单与模板驱动表单有着显著的不同点。响应式表单通过对数据模型的同步访问提供了更多的可预测性,使用 Observable 的操作符提供了不可变性,并且通过 Observable 流提供了变化追踪功能。
模板驱动表单允许你直接在模板中修改数据,但不像响应式表单那么明确,因为它们依赖嵌入到模板中的指令,并借助可变数据来异步跟踪变化。
使用表单控件有三个步骤。
FormControl
实例,并把它保存在组件中。FormControl
。然后,你可以把组件添加到模板中来显示表单。
下面的例子展示了如何添加一个表单控件。在这个例子中,用户在输入字段中输入自己的名字,捕获其输入值,并显示表单控件的当前值。
动作 | 详情 |
---|---|
注册响应式表单模块 | 要使用响应式表单控件,就要从 import { ReactiveFormsModule } from '@angular/forms'; |
生成新的 | 使用 CLI 命令 import { Component } from '@angular/core'; 可以用 |
在模板中注册该控件 | 在组件类中创建了控件之后,你还要把它和模板中的一个表单控件关联起来。修改模板,为表单控件添加 formControl 绑定,formControl 是由 ReactiveFormsModule 中的 FormControlDirective 提供的。<label for="name">Name: </label>使用这种模板绑定语法,把该表单控件注册给了模板中名为 name 的输入元素。这样,表单控件和 DOM 元素就可以互相通讯了:视图会反映模型的变化,模型也会反映视图中的变化。 |
显示该组件 | 把该组件添加到模板时,将显示指派给 <app-name-editor></app-name-editor> |
你可以用下列方式显示它的值。
valueChanges
,你可以在模板中使用 AsyncPipe
或在组件类中使用 subscribe()
方法来监听表单值的变化。value
属性。它能让你获得当前值的一份快照。下面的例子展示了如何在模板中使用插值显示当前值。
<p>Value: {{ name.value }}</p>
一旦你修改了表单控件所关联的元素,这里显示的值也跟着变化了。
响应式表单还能通过每个实例的属性和方法提供关于特定控件的更多信息。AbstractControl
的这些属性和方法用于控制表单状态,并在处理表单校验时决定何时显示信息。
响应式表单还有一些方法可以用编程的方式修改控件的值,它让你可以灵活的修改控件的值而不需要借助用户交互。FormControl
提供了一个 setValue()
方法,它会修改这个表单控件的值,并且验证与控件结构相对应的值的结构。比如,当从后端 API 或服务接收到了表单数据时,可以通过 setValue()
方法来把原来的值替换为新的值。
下列的例子往组件类中添加了一个方法,它使用 setValue()
方法来修改 Nancy 控件的值。
updateName() {
this.name.setValue('Nancy');
}
修改模板,添加一个按钮,用于模拟改名操作。在点 Update Name
按钮之前表单控件元素中输入的任何值都会回显为它的当前值。
<button type="button" (click)="updateName()">Update Name</button>
由于表单模型是该控件的事实之源,因此当你单击该按钮时,组件中该输入框的值也变化了,覆盖掉它的当前值。
注意:
在这个例子中,你只使用单个控件,但是当调用 FormGroup
或 FormArray
实例的 setValue()
方法时,传入的值就必须匹配控件组或控件数组的结构才行。
表单中通常会包含几个相互关联的控件。响应式表单提供了两种把多个相关控件分组到同一个输入表单中的方法。
表单组 | 详情 |
---|---|
表单分组 | 定义了一个带有一组控件的表单,你可以把它们放在一起管理。表单组的基础知识将在本节中讨论。你也可以通过嵌套表单组来创建更复杂的表单。 |
表单数组 | 定义了一个动态表单,你可以在运行时添加和删除控件。你也可以通过嵌套表单数组来创建更复杂的表单。 |
就像 FormControl
的实例能让你控制单个输入框所对应的控件一样,FormGroup
的实例也能跟踪一组 FormControl
实例(比如一个表单)的表单状态。当创建 FormGroup
时,其中的每个控件都会根据其名字进行跟踪。下面的例子展示了如何管理单个控件组中的多个 FormControl
实例。
生成一个 ProfileEditor
组件并从 @angular/forms
包中导入 FormGroup
和 FormControl
类。
ng generate component ProfileEditor
import { FormGroup, FormControl } from '@angular/forms';
要将表单组添加到此组件中,请执行以下步骤。
FormGroup
实例。FormGroup
模型关联到视图。动作 | 详情 |
---|---|
创建一个 | 在组件类中创建一个名叫 对此个人档案表单,要添加两个 import { Component } from '@angular/core'; 这些独立的表单控件被收集到了一个控件组中。这个 |
把这个 | 这个表单组还能跟踪其中每个控件的状态及其变化,所以如果其中的某个控件的状态或值变化了,父控件也会发出一次新的状态变更或值变更事件。该控件组的模型来自它的所有成员。在定义了这个模型之后,你必须更新模板,来把该模型反映到视图中。 <form [formGroup]="profileForm"> NOTE: 由 |
保存表单数据 | <form [formGroup]="profileForm" (ngSubmit)="onSubmit()"> onSubmit() { <p>Complete the form to enable button.</p> 注意: |
显示此组件 | 要显示包含此表单的 <app-profile-editor></app-profile-editor> |
表单组可以同时接受单个表单控件实例和其它表单组实例作为其子控件。这可以让复杂的表单模型更容易维护,并在逻辑上把它们分组到一起。
如果要构建复杂的表单,如果能在更小的分区中管理不同类别的信息就会更容易一些。使用嵌套的 FormGroup
可以让你把大型表单组织成一些稍小的、易管理的分组。
要制作更复杂的表单,请遵循如下步骤。
某些类型的信息天然就属于同一个组。比如名称和地址就是这类嵌套组的典型例子,下面的例子中就用到了它们。
动作 | 详情 |
---|---|
创建一个嵌套的表单组。 | 要在 profileForm 中创建一个嵌套组,就要把一个嵌套的 address 元素添加到此表单组的实例中。 import { Component } from '@angular/core'; 在这个例子中, |
在模板中对这个嵌套表单分组。 | 在修改了组件类中的模型之后,还要修改模板,来把这个 FormGroup 实例对接到它的输入元素。<div formGroupName="address"> ProfileEditor 表单显示为一个组,但是将来这个模型会被进一步细分,以表示逻辑分组区域。提示: 这里使用了 value 属性和 JsonPipe 管道在组件模板中显示了这个 FormGroup 的值。 |
当修改包含多个 FormGroup
实例的值时,你可能只希望更新模型中的一部分,而不是完全替换掉。这一节会讲解该如何更新 AbstractControl
模型中的一部分。
有两种更新模型值的方式:
方法 | 详情 |
---|---|
| 使用 |
| 使用 |
setValue()
方法的严格检查可以帮助你捕获复杂表单嵌套中的错误,而 patchValue()
在遇到那些错误时可能会默默的失败。
在 ProfileEditorComponent
中,使用 updateProfile
方法传入下列数据可以更新用户的名字与街道住址。
updateProfile() {
this.profileForm.patchValue({
firstName: 'Nancy',
address: {
street: '123 Drew Street'
}
});
}
通过往模板中添加一个按钮来模拟一次更新操作,以修改用户档案。
<button type="button" (click)="updateProfile()">Update Profile</button>
当点击按钮时,profileForm
模型中只有 firstName
和 street
被修改了。注意,street
是在 address
属性的对象中被修改的。这种结构是必须的,因为 patchValue()
方法要针对模型的结构进行更新。patchValue()
只会更新表单模型中所定义的那些属性。
当需要与多个表单打交道时,手动创建多个表单控件实例会非常繁琐。FormBuilder
服务提供了一些便捷方法来生成表单控件。FormBuilder
在幕后也使用同样的方式来创建和返回这些实例,只是用起来更简单。
通过下列步骤可以利用这项服务。
FormBuilder
类。FormBuilder
服务。下面的例子展示了如何重构 ProfileEditor
组件,用 FormBuilder
来代替手工创建这些 FormControl
和 FormGroup
实例。
动作 | 详情 |
---|---|
导入 FormBuilder 类 | 从 @angular/forms 包中导入 FormBuilder 类。import { FormBuilder } from '@angular/forms'; |
注入 FormBuilder 服务 | constructor(private fb: FormBuilder) { } |
生成表单控件 | import { Component } from '@angular/core'; 在上面的例子中,你可以使用 你可以只使用初始值来定义控件,但是如果你的控件还需要同步或异步验证器,那就在这个数组中的第二项和第三项提供同步和异步验证器。 比较一下用表单构建器和手动创建实例这两种方式。
profileForm = new FormGroup({src/app/profile-editor/profile-editor.component.ts (form builder) profileForm = this.fb.group({ |
表单验证用于确保用户的输入是完整和正确的。本节讲解了如何把单个验证器添加到表单控件中,以及如何显示表单的整体状态。
使用下列步骤添加表单验证。
最常见的验证是做一个必填字段。下面的例子给出了如何在 firstName
控件中添加必填验证并显示验证结果的方法。
操作 | 详情 |
---|---|
导入验证器函数 | 响应式表单包含了一组开箱即用的常用验证器函数。这些函数接收一个控件,用以验证并根据验证结果返回一个错误对象或空值。 从 import { Validators } from '@angular/forms'; |
建一个必填字段 | 在 profileForm = this.fb.group({ |
显示表单状态 | 当你往表单控件上添加了一个必填字段时,它的初始值是无效的(invalid)。这种无效状态会传播到其父 FormGroup 元素中,也让这个 FormGroup 的状态变为无效的。你可以通过该 FormGroup 实例的 status 属性来访问其当前状态。<p>Form Status: {{ profileForm.status }}</p>
|
FormArray
是 FormGroup
之外的另一个选择,用于管理任意数量的匿名控件。像 FormGroup
实例一样,你也可以往 FormArray
中动态插入和移除控件,并且 FormArray
实例的值和验证状态也是根据它的子控件计算得来的。不过,你不需要为每个控件定义一个名字作为 key,因此,如果你事先不知道子控件的数量,这就是一个很好的选择。
要定义一个动态表单,请执行以下步骤。
FormArray
类。FormArray
控件。FormArray
控件。下面的例子展示了如何在 ProfileEditor
中管理别名数组。
操作 | 详情 |
---|---|
导入 FormArray 类 | 从 import { FormArray } from '@angular/forms'; |
定义 FormArray 控件 | 你可以通过把一组(从零项到多项)控件定义在一个数组中来初始化一个 使用 profileForm = this.fb.group({
|
访问 FormArray 控件 | 相对于重复使用 profileForm.get() 方法获取每个实例的方式,getter 可以让你轻松访问表单数组各个实例中的别名。表单数组实例用一个数组来代表未定数量的控件。通过 getter 来访问控件很方便,这种方法还能很容易地重复处理更多控件。使用 getter 语法创建类属性 aliases ,以从父表单组中接收表示绰号的表单数组控件。get aliases() { 注意: addAlias() {在这个模板中,这些控件会被迭代,把每个控件都显示为一个独立的输入框。 |
在模板中显示表单数组 | 要想为表单模型添加 aliases ,你必须把它加入到模板中供用户输入。和 FormGroupNameDirective 提供的 formGroupName 一样,FormArrayNameDirective 也使用 formArrayName 在这个 FormArray 实例和模板之间建立绑定。在 formGroupName <div> 元素的结束标签下方,添加一段模板 HTML。<div formArrayName="aliases"> *ngFor 指令对 aliases FormArray 提供的每个 FormControl 进行迭代。因为 FormArray 中的元素是匿名的,所以你要把索引号赋值给 i 变量,并且把它传给每个控件的 formControlName 输入属性。每当新的 alias 加进来时,FormArray 的实例就会基于这个索引号提供它的控件。这将允许你在每次计算根控件的状态和值时跟踪每个控件。 |
添加一个别名 | 最初,表单只包含一个绰号字段,点击 除了为每个绰号使用 FormControl 之外,你还可以改用 FormGroup 来组合上一些额外字段。对其中的每个条目定义控件的过程和前面没有区别。 |
下表给出了用于创建和管理响应式表单控件的基础类和服务。
类 | 详情 |
---|---|
| 所有三种表单控件类( |
| 管理单体表单控件的值和有效性状态。它对应于 HTML 的表单控件,比如 |
| 管理一组 |
| 管理一些 |
| 一个可注入的服务,提供一些用于提供创建控件实例的工厂方法。 |
指令 | 详情 |
---|---|
FormControlDirective | 把一个独立的 |
FormControlName | 把一个现有 |
FormGroupDirective | 把一个现有的 |
FormGroupName | 把一个内嵌的 |
FormArrayName | 把一个内嵌的 |