您的位置:首页 > Web前端 > AngularJS

angular2-响应式表单

2017-11-08 14:51 417 查看
响应式表单是同步的。模板驱动表单是异步的。这个不同点很重要

使用响应式表单,我们会在代码中创建整个表单控件树。 我们可以立即更新一个值或者深入到表单中的任意节点,因为所有的控件都始终是可用的。

模板驱动表单会委托指令来创建它们的表单控件。 为了消除“检查完后又变化了”的错误,这些指令需要消耗一个以上的变更检测周期来构建整个控件树。 这意味着在从组件类中操纵任何控件之前,我们都必须先等待一个节拍。

比如,如果我们用
@ViewChild(NgForm)
查询来注入表单控件,并在 生命周期钩子
ngAfterViewInit
中检查它,就会发现它没有子控件。 我们必须使用
setTimeout
等待一个节拍才能从控件中提取值、测试有效性,或把它设置为新值。

导入
ReactiveFormsModule

基础的表单类

AbstractControl
是三个具体表单类的抽象基类。 并为它们提供了一些共同的行为和属性,其中有些是可观察对象(Observable)。

FormControl 用于跟踪一个单独的表单控件的值和有效性状态。它对应于一个HTML表单控件,比如输入框和下拉框。

FormGroup用于 跟踪一组
AbstractControl
的实例的值和有效性状态。 该组的属性中包含了它的子控件。 组件中的顶级表单就是一个
FormGroup


FormArray用于跟踪
AbstractControl
实例组成的有序数组的值和有效性状态。

添加FormGroup

多个FormControl,我们会希望把它们注册进一个父
FormGroup
中。这很容易。只要把它加入
hero-detail.component.ts
import
区就可以了

export class HeroDetailComponent2 {
heroForm = new FormGroup ({
name: new FormControl()
});
}


  

<form [formGroup]="heroForm" novalidate>
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
</form>


<form>
元素上的
novalidate
属性会阻止浏览器使用原生HTML中的表单验证器。

formGroup
是一个响应式表单的指令,它拿到一个现有
FormGroup
实例,并把它关联到一个HTML元素上。 这种情况下,它关联到的是
form
元素上的
FormGroup
实例
heroForm


  heroForm.value ==> { name : xxxx, ....... }

FormBuilder:

FormBuilder
类能通过处理控件创建的细节问题来帮我们减少重复劳动。

import { FormBuilder, FormGroup } from '@angular/forms';

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"
});
}
}


FormBuilder.group
是一个用来创建
FormGroup
的工厂方法,它接受一个对象,对象的键和值分别是
FormControl
的名字和它的定义。 在这个例子中,
name
控件的初始值是空字符串。

要想让
name
这个
FormControl
是必须的,请把
FormGroup
中的
name
属性改为一个数组。第一个条目是
name
的初始值,第二个是
required
验证器:
Validators.required


this.heroForm = this.fb.group({
name: ['', Validators.required ],
});


  

多级
FormGroup

export class HeroDetailComponent5 {
heroForm: FormGroup;
states = states;

constructor(private fb: FormBuilder) {
this.createForm();
}

createForm() {
this.heroForm = this.fb.group({ // <-- the parent FormGroup
name: ['', Validators.required ],
address: this.fb.group({ // <-- the child FormGroup
street: '',
city: '',
state: '',
zip: ''
}),
power: '',
sidekick: ''
});
}
}


<div formGroupName="address" class="well well-lg">
<input class="form-control" formControlName="street">
<input class="form-control" formControlName="city">
<select class="form-control" formControlName="state">
<input class="form-control" formControlName="zip">
</div>


做完这些之后,浏览器中的JSON输出就变成了带有多级
FormGroup
的住址。



查看
FormControl
的属性

使用
.get()
方法来提取表单中一个单独
FormControl
的状态

<p>Name value: {{ heroForm.get('name').value }}</p>


要点取得
FormGroup
中的
FormControl
的状态,使用点语法来指定到控件的路径

<p>Street value: {{ heroForm.get('address.street').value}}</p>


使用此技术来显示
FromControl
的任意属性,代码如下

属性

说明

myControl.value
FormControl
的值。

myControl.status
FormControl
的有效性。可能的值有
VALID
INVALID
PENDING
DISABLED


myControl.pristine
如果用户尚未改变过这个控件的值,则为
true
。它总是与
myControl.dirty
相反。

myControl.untouched
true
如果用户尚未进入这个HTML控件,也没有触发过它的
blur
(失去焦点)事件,则为
true
。 它是
myControl.touched
的反义词。

数据模型与表单模型

自服务器的
hero
就是数据模型,而
FormControl
的结构就是表单模型

组件必须把数据模型中的英雄值复制到表单模型中。这里隐含着两个非常重要的点。

开发人员必须理解数据模型是如何映射到表单模型中的属性的。

用户修改时的数据流是从DOM元素流向表单模型的,而不是数据模型。表单控件永远不会修改数据模型。

// 数据模型
export class Hero {
id = 0;
name = '';
addresses: Address[];
}

export class Address {
street = '';
city   = '';
state  = '';
zip    = '';
}

//表单模型
this.heroForm = this.fb.group({
name: ['', Validators.required ],
address: this.fb.group(new Address()),
power: '',
sidekick: ''
});


在这些模型中有两点显著的差异:

Hero
有一个
id
。表单模型中则没有,因为我们通常不会把主键展示给用户。

Hero
有一个住址数组。这个表单模型只表示了一个住址,稍后的修改则可以表示多个。

使用
setValue
patchValue
来操纵表单模型

setValue 方法

this.heroForm.setValue({
name:    this.hero.name,
address: this.hero.addresses[0] || new Address()  //(只能显示英雄的第一个住址,不过我们还必须考虑
hero
完全没有住址的可能性)
});


setValue
方法会在赋值给任何表单控件之前先检查数据对象的值。

它不会接受一个与FormGroup结构不同或缺少表单组中任何一个控件的数据对象。

如果我们有什么拼写错误或控件嵌套的不正确,它就能返回一些有用的错误信息。
patchValue
会默默地失败

patchValue 方法

借助
patchValue
,我们可以通过提供一个只包含要更新的控件的键值对象来把值赋给
FormGroup
中的指定控件

this.heroForm.patchValue({
name: this.hero.name
});


  

什么时候设置表单的模型值(
ngOnChanges

我们可以在ngOnChanges钩子中调用
setValue
,就像例子中所演示的那样, 每当输入属性
hero
发生变化时,Angular就会调用它。

重置表单的标识。

我们应该在更换英雄的时候重置表单,以便来自前一个英雄的控件值被清除,并且其状态被恢复为
pristine
(原始)状态。 我们可以在
ngOnChanges
的顶部调用
reset
,就像这样

this.heroForm.reset();


reset
方法有一个可选的
state
值,让我们能在重置状态的同时顺便设置控件的值。 在内部实现上,
reset
会把该参数传给了
setValue
。 略微重构之后,
ngOnChanges
会变成这样

ngOnChanges() {
this.heroForm.reset({
name: this.hero.name,
address: this.hero.addresses[0] || new Address()
});
}


  

使用
FormArray
来表示
FormGroup
数组

Hero.addresses
属性就是一个
Address
实例的数组。 一个住址的
FormGroup
可以显示一个
Address
对象。 而
FormArray
可以显示一个住址
FormGroup
的数组

import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';

要想使用
FormArray
,我们要这么做:

在数组中定义条目(
FormControl
FormGroup
)。

把这个数组初始化微一组从数据模型中的数据创建的条目。

根据用户的需求添加或移除这些条目。

从用户的视角来看,英雄们没有住址。 只有我们凡人才有住址,英雄们拥有的是秘密小屋! 把
FormGroup
型的住址替换为
FormArray
型的
secretLairs
定义:

this.heroForm = this.fb.group({
name: ['', Validators.required ],
secretLairs: this.fb.array([]), // <-- secretLairs as an empty FormArray
power: '',
sidekick: ''
});


  

表单的控件名从
address
改为
secretLairs
让我们遇到了一个重要问题:表单模型与数据模型不再匹配了。

显然,必须在两者之间建立关联。但它在应用领域中的意义不限于此,它可以用于任何东西。

展现的需求经常会与数据的需求不同。 响应式表单的方法既强调这种差异,也能为这种差异提供了便利。

初始化
FormArray
型的
secretLairs

下面的
setAddresses
方法把
secretLairs
数组替换为一个新的
FormArray
,使用一组表示英雄地址的
FormGroup
来进行初始化。

setAddresses(addresses: Address[]) {
const addressFGs = addresses.map(address => this.fb.group(address));
const addressFormArray = this.fb.array(addressFGs);
this.heroForm.setControl('secretLairs', addressFormArray);
}


注意,我们使用
FormGroup.setControl
方法,而不是
setValue
方法来设置前一个
FormArray
。 我们所要替换的是控件,而不是控件的值。  

获取
FormArray

使用
FormGroup.get
方法来获取到
FormArray
的引用。 把这个表达式包装进一个名叫
secretLairs
的便捷属性中来让它更清晰,并供复用

this.heroForm = this.fb.group({
  name: '',
  secretLairs: this.fb.array([]),
  power: '',
  sidekick: ''
});

get secretLairs(): FormArray {
return this.heroForm.get('secretLairs') as FormArray;
};


  

显示
FormArray

当前HTML模板显示单个的地址
FormGroup
。 我们要把它修改成能显示0、1或更多个表示英雄地址的
FormGroup


要改的部分主要是把以前表示地址的HTML模板包裹进一个
<div>
中,并且使用
*ngFor
来重复渲染这个
<div>


诀窍在于要知道如何编写
*ngFor
。主要有三点:

*ngFor
<div>
之外套上另一个包装
<div>
,并且把它的
formArrayName
指令设为
"secretLairs"
。 这一步为内部的表单控件建立了一个
FormArray
型的
secretLairs
作为上下文,以便重复渲染HTML模板。

这些重复条目的数据源是
FormArray.controls
而不是
FormArray
本身。 每个控件都是一个
FormGroup
型的地址对象,与以前的模板HTML所期望的格式完全一样。

每个被重复渲染的
FormGroup
都需要一个独一无二的
formGroupName
,它必须是
FormGroup
在这个
FormArray
中的索引。 我们将复用这个索引,以便为每个地址组合出一个独一无二的标签。

<div formArrayName="secretLairs" class="well well-lg">
<div *ngFor="let address of secretLairs.controls; let i=index" [formGroupName]="i" >
<input class="form-control" formControlName="street">
<input class="form-control" formControlName="city">
<select class="form-control" formControlName="state">
<input class="form-control" formControlName="zip">
</div>
</div>


  

添加一个
addLair
方法,它获取
secretLairs
数组,并把新的表示地址的
FormGroup
添加到其中。

addLair() {
this.secretLairs.push(this.fb.group(new Address()));
}

<button (click)="addLair()" type="button">Add a Secret Lair</button>

//务必确保添加了type="button"属性。 事实上,我们应该总是指定按钮的type。 如果不明确指定类型,按钮的默认类型就是“submit”(提交)。
当我们稍后添加了表单提交的动作时,每个“submit”按钮都是触发一次提交操作,而它将可能会做一些处理,比如保存当前的修改。
我们显然不会希望每当用户点击“Add a Secret Lair”按钮时就保存一次。


 

监视控件的变化

用户在父组件
HeroListComponent
中选取了一个英雄,Angular就会调用一次
ngOnChanges


用户修改英雄的名字或秘密小屋时,Angular并不会调用
ngOnChanges


通过订阅表单控件的属性之一来了解这些变化,此属性会发出变更通知,
valueChanges
,可以返回一个RxJS的
Observable
对象。

添加下列方法,以监听姓名这个
FormControl
中值的变化

nameChangeLog: string[] = [];
logNameChange() {
const nameControl = this.heroForm.get('name');
nameControl.valueChanges.forEach(
(value: string) => this.nameChangeLog.push(value)
);
}


在构造函数中调用它,就在创建表单的代码之后

constructor(private fb: FormBuilder) {
this.createForm();
this.logNameChange();
}


  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: