1,添加单个表单控件:
步骤 1 - 注册 ReactiveFormsModule
要使用响应式表单,就要从 @angular/forms
包中导入 ReactiveFormsModule
并把它添加到你的 NgModule 的 imports
数组中。
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
imports: [
// other imports ...
ReactiveFormsModule
],
})
export class AppModule { }
步骤 2 - 生成并导入一个新的表单控件
为该控件生成一个组件
ng generate component NameEditor
当使用响应式表单时,FormControl
类是最基本的构造块。要注册单个的表单控件,请在组件中导入 FormControl
类,并创建一个 FormControl
的新实例,把它保存在类的某个属性中。
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-name-editor',
templateUrl: './name-editor.component.html',
styleUrls: ['./name-editor.component.css']
})
export class NameEditorComponent {
name = new FormControl('');//此处保存fromcontrol到组件的那么属性当中
}
步骤 3 - 在模板中注册该控件
在组件类中创建了控件之后,你还要把它和模板中的一个表单控件关联起来。修改模板,为表单控件添加 formControl
绑定,formControl
是由 ReactiveFormsModule
中的 FormControlDirective
提供的。
<label>
Name:
<input type="text" [formControl]="name"> //使用这种模板绑定语法,把该表单控件注册给了模板中名为 name
的输入元素。这样,表单控件和 DOM 元素就可以互相通讯了:视图会反映模型的变化,模型也会反映视图中的变化。
</label>
显示表单控件的值
你可以用两种方式显示它的值:
-
通过可观察对象
valueChanges
,你可以在模板中使用AsyncPipe
或在组件类中使用subscribe()
方法来监听表单值的变化。 -
使用
value
属性。它能让你获得当前值的一份快照。
<p>
Value: {{ name.value }}
</p>
一旦你修改了表单控件所关联的元素,这里显示的值也跟着变化了。
替换表单控件的值
响应式表单还有一些方法可以用编程的方式修改控件的值,它让你可以灵活的修改控件的值而不需要借助用户交互。FormControl
提供了一个 setValue()
方法,它会修改这个表单控件的值,并且验证与控件结构相对应的值的结构。比如,当从后端 API 或服务接收到了表单数据时,可以通过 setValue()
方法来把原来的值替换为新的值。
下列的例子往组件类中添加了一个方法,它使用 setValue()
方法来修改 Nancy 控件的值。
updateName() {
this.name.setValue('Nancy');
}
2,把表单控件分组
1生成一个 ProfileEditor
组件并从 @angular/forms
包中导入 FormGroup
和 FormControl
类。
2 创建 FormGroup
实例
在组件类中创建一个名叫 profileForm
的属性,并设置为 FormGroup
的一个新实例。要初始化这个 FormGroup
,请为构造函数提供一个由控件组成的对象,对象中的每个名字都要和表单控件的名字一一对应。
对此个人档案表单,要添加两个 FormControl
实例,名字分别为 firstName
和 lastName
。
import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
@Component({
selector: 'app-profile-editor',
templateUrl: './profile-editor.component.html',
styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
profileForm = new FormGroup({ //此处的名称要和模板中的名字一样
firstName: new FormControl(''), //此处的名称要和模板中的名字一样
lastName: new FormControl(''), //此处的名称要和模板中的名字一样
});
}
现在,这些独立的表单控件被收集到了一个控件组中。这个 FormGroup
用对象的形式提供了它的模型值,这个值来自组中每个控件的值。 FormGroup
实例拥有和 FormControl
实例相同的属性(比如 value
、untouched
)和方法(比如 setValue()
)。
3关联 FormGroup
的模型和视图
这个表单组还能跟踪其中每个控件的状态及其变化,所以如果其中的某个控件的状态或值变化了,父控件也会发出一次新的状态变更或值变更事件。该控件组的模型来自它的所有成员。在定义了这个模型之后,你必须更新模板,来把该模型反映到视图中。
<form [formGroup]="profileForm">//此处profileForm和组件中的名称保持一致
<label>
First Name:
<input type="text" formControlName="firstName"> //此处firstName和组件中的名称保持一致
</label>
<label>
Last Name:
<input type="text" formControlName="lastName">/此处lastName和组件中的名称保持一致
</label>
</form>
注意,就像 FormGroup
所包含的那控件一样,profileForm 这个 FormGroup
也通过 FormGroup
指令绑定到了 form
元素,在该模型和表单中的输入框之间创建了一个通讯层。 由 FormControlName
指令提供的 formControlName
属性把每个输入框和 FormGroup
中定义的表单控件绑定起来。这些表单控件会和相应的元素通讯,它们还把更改传递给 FormGroup
,这个 FormGroup
是模型值的权威数据源。
保存表单数据
ProfileEditor
组件从用户那里获得输入,但在真实的场景中,你可能想要先捕获表单的值,等将来在组件外部进行处理。FormGroup
指令会监听 form
元素发出的 submit
事件,并发出一个 ngSubmit
事件,让你可以绑定一个回调函数。
把 onSubmit()
回调方法添加为 form
标签上的 ngSubmit
事件监听器。
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
3,嵌套的表单组
步骤 1 - 创建嵌套的分组
“地址”就是可以把信息进行分组的绝佳范例。FormGroup
可以同时接纳 FormControl
和 FormGroup
作为子控件。这使得那些比较复杂的表单模型可以更易于维护、更有逻辑性。要想在 profileForm
中创建一个嵌套的分组,请添加一个内嵌的名叫 address
的元素指向这个 FormGroup
实例。
import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
@Component({
selector: 'app-profile-editor',
templateUrl: './profile-editor.component.html',
styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
profileForm = new FormGroup({ //此处是FormGroup
firstName: new FormControl(''),
lastName: new FormControl(''),
address: new FormGroup({//FormGroup下面包含了一个FormGroup
street: new FormControl(''),
city: new FormControl(''),
state: new FormControl(''),
zip: new FormControl('')
})
});
}
在这个例子中,address group
把现有的 firstName
、lastName
控件和新的 street
、city
、state
和 zip
控件组合在一起。虽然 address
这个 FormGroup
是 profileForm
这个整体 FormGroup
的一个子控件,但是仍然适用同样的值和状态的变更规则。来自内嵌控件组的状态和值的变更将会冒泡到它的父控件组,以维护整体模型的一致性。
步骤 2 - 在模板中分组内嵌的表单
在修改了组件类中的模型之后,还要修改模板,来把这个 FormGroup
实例对接到它的输入元素。
把包含 street
、city
、state
和 zip
字段的 address
表单组添加到 ProfileEditor
模板中。
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
<label>
First Name:
<input type="text" formControlName="firstName" required>
</label>
<label>
Last Name:
<input type="text" formControlName="lastName">
</label>
<div formGroupName="address">
<h3>Address</h3>
<label>
Street:
<input type="text" formControlName="street">
</label>
<label>
City:
<input type="text" formControlName="city">
</label>
<label>
State:
<input type="text" formControlName="state">
</label>
<p>
Form Value: {{ profileForm.value | json }}
</p>
<p>
Form Status: {{ profileForm.status }}
</p>
<p>
<button (click)="updateProfile()">Update Profile</button>
</p>
部分模型更新
当修改包含多个 FormGroup
实例的值时,你可能只希望更新模型中的一部分,而不是完全替换掉。这一节会讲解该如何更新 AbstractControl
模型中的一部分。
修补(Patching)模型值
有两种更新模型值的方式:
-
使用
setValue()
方法来为单个控件设置新值。setValue()
方法会严格遵循表单组的结构,并整体性替换控件的值。 -
使用
patchValue()
方法可以用对象中所定义的任何属性为表单模型进行替换。
setValue()
方法的严格检查可以帮助你捕获复杂表单嵌套中的错误,而 patchValue()
在遇到那些错误时可能会默默的失败。
在 ProfileEditorComponent
中,使用 updateProfile
方法传入下列数据可以更新用户的名字与街道住址。
updateProfile() {
this.profileForm.patchValue({
firstName: 'Nancy',
address: {
street: '123 Drew Street'
}
});
}
当点击按钮时,profileForm
模型中只有 firstName
和 street
被修改了。注意,street
是在 address
属性的对象中被修改的。这种结构是必须的,因为 patchValue()
方法要针对模型的结构进行更新。patchValue()
只会更新表单模型中所定义的那些属性。
4,使用 FormBuilder
来生成表单控件
当需要与多个表单打交道时,手动创建多个表单控件实例会非常繁琐。FormBuilder
服务提供了一些便捷方法来生成表单控件。FormBuilder
在幕后也使用同样的方式来创建和返回这些实例,只是用起来更简单。
步骤 1 - 导入 FormBuilder
类
从 @angular/forms
包中导入 FormBuilder
类。
import { FormBuilder } from '@angular/forms';
步骤 2 - 注入 FormBuilder
服务
FormBuilder
是一个可注入的服务提供商,它是由 ReactiveFormModule
提供的。只要把它添加到组件的构造函数中就可以注入这个依赖。
constructor(private fb: FormBuilder) { }
步骤 3 - 生成表单控件
FormBuilder
服务有三个方法:control()
、group()
和 array()
。这些方法都是工厂方法,用于在组件类中分别生成 FormControl
、FormGroup
和 FormArray
。
用 group
方法来创建 profileForm
控件。
import { Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';
@Component({
selector: 'app-profile-editor',
templateUrl: './profile-editor.component.html',
styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
profileForm = this.fb.group({//创建表单组
firstName: [''],
lastName: [''],
address: this.fb.group({//创建表单组
street: [''],
city: [''],
state: [''],
zip: ['']
}),
});
在上面的例子中,你可以使用 group()
方法,用和前面一样的名字来定义这些属性。这里,每个控件名对应的值都是一个数组,这个数组中的第一项是其初始值。
constructor(private fb: FormBuilder) { }
}
5,简单表单验证
步骤 1 - 导入验证器函数
响应式表单包含了一组开箱即用的常用验证器函数。这些函数接收一个控件,用以验证并根据验证结果返回一个错误对象或空值。
从 @angular/forms
包中导入 Validators
类。
import { Validators } from '@angular/forms';
步骤 2 - 把字段设为必填(required)
最常见的校验项是把一个字段设为必填项。本节描述如何为 firstName
控件添加“必填项”验证器。
在 ProfileEditor
组件中,把静态方法 Validators.required
设置为 firstName
控件值数组中的第二项。
profileForm = this.fb.group({
firstName: ['', Validators.required],//此处设置firstName为必填项
lastName: [''],
address: this.fb.group({
street: [''],
city: [''],
state: [''],
zip: ['']
}),
});
HTML5 有一组内置的属性,用来进行原生验证,包括 required
、minlength
、maxlength
等。虽然是可选的,不过你也可以在表单的输入元素上把它们添加为附加属性来使用它们。这里我们把 required
属性添加到 firstName
输入元素上。
<input type="text" formControlName="firstName" required>
显示表单状态
当你往表单控件上添加了一个必填字段时,它的初始值是无效的(invalid)。这种无效状态会传播到其父 FormGroup
元素中,也让这个 FormGroup
的状态变为无效的。你可以通过该 FormGroup
实例的 status
属性来访问其当前状态。
<p>
Form Status: {{ profileForm.status }}
</p>
提交按钮被禁用了,因为 firstName
控件的必填项规则导致了 profileForm
也是无效的。在你填写了 firstName
输入框之后,该表单就变成了有效的,并且提交按钮也启用了。
6,使用表单数组管理动态控件
FormArray
是 FormGroup
之外的另一个选择,用于管理任意数量的匿名控件。像 FormGroup
实例一样,你也可以往 FormArray
中动态插入和移除控件,并且 FormArray
实例的值和验证状态也是根据它的子控件计算得来的。 不过,你不需要为每个控件定义一个名字作为 key,因此,如果你事先不知道子控件的数量,这就是一个很好的选择。下面的例子展示了如何在 ProfileEditor
中管理一组绰号(aliases)。
步骤 1 - 导入 FormArray
从 @angular/form
中导入 FormArray
,以使用它的类型信息。FormBuilder
服务用于创建 FormArray
实例。
import { FormArray } from '@angular/forms';
步骤 2 - 定义 FormArray
你可以通过把一组(从零项到多项)控件定义在一个数组中来初始化一个 FormArray
。为 profileForm
添加一个 aliases
属性,把它定义为 FormArray
类型。
使用 FormBuilder.array()
方法来定义该数组,并用 FormBuilder.control()
方法来往该数组中添加一个初始控件。
profileForm = this.fb.group({
firstName: ['', Validators.required],
lastName: [''],
address: this.fb.group({
street: [''],
city: [''],
state: [''],
zip: ['']
}),
aliases: this.fb.array([
this.fb.control('')//为formArray添加一个初始组件
])
});
FormGroup
中的这个 aliases
控件现在管理着一个控件,将来还可以动态添加多个。
步骤 3 - 访问 FormArray
控件
相对于重复使用 profileForm.get()
方法获取每个实例的方式,getter 可以让你轻松访问表单数组各个实例中的别名。 表单数组实例用一个数组来代表未定数量的控件。通过 getter 来访问控件很方便,这种方法还能很容易地重复处理更多控件。
使用 getter 语法创建类属性 aliases
,以从父表单组中接收表示绰号的表单数组控件。
get aliases() {
return this.profileForm.get('aliases') as FormArray;
}
定义一个方法来把一个绰号控件动态插入到绰号 FormArray
中。用 FormArray.push()
方法把该控件添加为数组中的新条目。
addAlias() {
this.aliases.push(this.fb.control(''));
}
步骤 4 - 在模板中显示表单数组
要想为表单模型添加 aliases
,你必须把它加入到模板中供用户输入。和 FormGroupNameDirective
提供的 formGroupName
一样,FormArrayNameDirective
也使用 formArrayName
在这个 FormArray
实例和模板之间建立绑定。
在 formGroupName
<div>
元素的结束标签下方,添加一段模板 HTML。
<div formArrayName="aliases">
<h3>Aliases</h3> <button (click)="addAlias()">Add Alias</button>
<div *ngFor="let address of aliases.controls; let i=index">
<!-- The repeated alias template -->
<label>
Alias:
<input type="text" [formControlName]="i">
</label>
</div>
</div>
每当新的 alias
加进来时,FormArray
的实例就会基于这个索引号提供它的控件。这将允许你在每次计算根控件的状态和值时跟踪每个控件。
添加绰号
最初,表单只包含一个绰号字段,点击 Add Alias
按钮,就出现了另一个字段。你还可以验证由模板底部的“Form Value”显示出来的表单模型所报告的这个绰号数组。