对上文的总结就是,相比ng1中数据的双向绑定,ng2保留了这个双向绑定能力(底层其实优化了很多),原先的ng-model指令升级成了ngModel,使用的功能保持不变。
同时尽管ng2版本的数据双向绑定得到了很大的优化,仍改变不了其数据异步绑定的方式,因为ng2不能确定数据何时绑定,我们也不能确定很多网络请求得到的数据到来的时间。
在ng1中其实这个机制会有一些尴尬的场景,至少笔者在一些情况下不得不在一些业务场景下使用setTimeout来保证数据已经成功绑定进入scope的watch循环,但这个异步绑定数据又是不可避免的,除非我们自己来适应实际项目改写angular代码了。
所以ng2就提供了让我们配合具体项目场景改写ngModel的能力,也就是原文介绍的响应式表单。
其跟ngModel的关系就是,ngModel是响应式表单的官方实现,其在我们绑定数据时自动为我们实现响应式表单中用到的几个机制,如果我们需要数据严格的实时同步绑定,就不必使用ngModel,可以亲自来编写响应式的表单,步骤覆盖了组件模板到数据模型类整条龙,而这么多事情在合适的场景下使用ngModel已经可以实现了,这两种表单绑定的方式各有其优势。
1. 使用响应式表单
响应式表单的能力封装在ReactiveFormsModule中,并且跟FormsModule同时包含在@angular/forms这个包中。
表单类的要点:
1.AbstractControl是FormControl、FormGroup、FormArray这三个实例表单类的抽象基类。它提供了他们的通用行为以及属性,其中就有observable。
2.FormControl在单个表单控件中检查值并验证状态。它负责将其通知给HTML表单控件(比如input这些)。
3.FormGroup负责AbstractControl实例的一个组的值与验证状态。组的属性包含了它们的子控件。你的组件的顶级表单就是一个FormGroup。
4.FormArray负责AbstractControl实例的数值索引数组的值与状态验证。
2. FormControl
最核心的指令就是FormControl,算是底层的ngModel,在模板标签中跟定义好的数据模型字段绑定起来,就像这样:
<h2>Hero Detail</h2> <h3><i>Just a FormControl</i></h3> <label>Name: <input [formControl]="name"> </label>
同时在组件代码中需要将上例中这个name字段声明为FormControl类:
export class HeroDetailComponent1 { name = new FormControl(); }
3. FormGroup
将多个FormControl对象分组到FormGroup中,用来方便管理。定义方法如下:
import { Component } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; export class HeroDetailComponent2 { heroForm = new FormGroup ({ name: new FormControl() }); }
此时模板标签中也要分组来写:
<h2>Hero Detail</h2> <h3><i>FormControl in a FormGroup</i></h3> <form [formGroup]="heroForm" novalidate> <div> <label>Name: <input formControlName="name"> </label> </div> </form>
现在的效果就是可以从heroForm中实时读取到其值和一些附加的状态的变化。
可以在模板中添加两个标签来展示数据的更改:
<p>Form value: {{ heroForm.value | json }}</p> <p>Form status: {{ heroForm.status | json }}</p>
4. FormBuilder
还有个新概念就是FormBuilder,是用来帮助创建表单类的:
1. 显示声明heroForm属性的类型为FormGroup,后面你将会初始化它
2. 注入FormBuilder到构造器中
3. 使用FormsBuilder添加新方法定义heroForm,叫做createForm。
4. 在构造器中执行createForm方法。
export class HeroDetailComponent3 { heroForm: FormGroup; // <--- heroForm is of type FormGroup constructor(private fb: FormBuilder) { // <--- inject FormBuilder this.createForm(); } createForm() { this.heroForm = this.fb.group({ name: '', // <--- the FormControl called "name" }); } }
上例中执行createForm方法即可动态快速的创建出表单类,其在一些表单类需要更改的场景下可以使用。
5. setValue和patchValue
这两个方法是真正给表单模型赋值用的。因为表单显示的数据与真实的底层数据肯定不能使同一个,否则表单输入数据一旦更改,源数据就被污染了,而这两个方法就是用来将源数据赋值到表单模型数据上的。
每当需要赋值时就可以调用,其中setValue必须准确赋值,并且会在数据不匹配时报告错误;而patchValue没有这么严格,但可以传一个对象,且不匹配时不会报告错误。
而我们要做的就是在ng2组件的ngOnChanges回调中手动执行setValue设置数据值。比如这样:
ngOnChanges() this.heroForm.setValue({ name: this.hero.name, address: this.hero.addresses[0] || new Address() }); }
同时ng2还提供了一个reset方法来重新调用setValue方法(setValue本身好像只是用来一次性赋值的)。
6. FormArray
FormArray是用来对付FormGroups列表的,比如一个英雄有可能有多个address字段,address字段本身就是个FormGroup,此时就要用到FormArray了:
this.heroForm = this.fb.group({ name: ['', Validators.required ], secretLairs: this.fb.array([]), // <-- secretLairs as an empty FormArray power: '', sidekick: '' });
获取FormArray要用到FormGroup提供的一个get方法:
get secretLairs(): FormArray { return this.heroForm.get('secretLairs') as FormArray; };
其显示的模板如下: