FormControl和FormGroup是Angular中两个最基础的表单对象。
FormControl代表单一的输入字段,它是Angular表单中的最小单元。
FormControl封装了这些字段的值和状态,比如是否有效、是否脏(被修改过)或是否有错
误等。
比如,下列代码演示了如何在TypeScript中使用FormControl:
// create a new FormControl with the value "Nate"
let nameControl = new FormControl("Nate");
let name = nameControl.value; // -> Nate
// now we can query this control for certain values:
nameControl.errors // -> StringMap of errors
nameControl.dirty // -> false
nameControl.valid // -> true
// etc.
为了构建表单,我们会创建几组FormControl对象,然后为它们附加元数据和逻辑。
在Angular中,我们经常将一个类(本例中为FormControl)以属性形式(本例中为formControl)附加在DOM上。比如下面这个表单:
<input type="text" [formControl]="name" />
这会在此form的上下文中创建一个新的FormControl对象。
大多数表单都拥有不止一个字段,因此我们需要某种方式来管理多个FormControl。假设我们要检查表单的有效性。如果要遍历这个FormControl数组并检查每一个FormControl是否有效,必然相当繁琐;而FormGroup则可以为一组FormControl提供总包接口(wrapper interface),来解决这种问题。
下面是FormGroup的创建方式:
let personInfo = new FormGroup({
firstName: new FormControl("Nate"),
lastName: new FormControl("Murray"),
zip: new FormControl("90210")
})
FormGroup 和 FormControl 都继承自同一个祖先AbstractControl。这意味检查 personInfo 的状态或值就像检查单个FormControl那么容易:
// firstName: "Nate",
// lastName: "Murray",
// zip: "90210"
// }
// now we can query this control group for certain values, which have sensible
// values depending on the children FormControl's values:
personInfo.errors // -> StringMap of errors
personInfo.dirty // -> false
personInfo.valid // -> true
// etc.
注意,当我们试图从FormGroup中获取value时,会收到一个“键值对”结构的对象。它能让我们从表单中一次性获取全部的值而无需逐一遍历FormControl,使用起来相当顺手。
我们要构建一个表单,效果如图:
为了使用这个新的表单库,先要确保我们的NgModule中导入了这个表单库。
Angular中有两种使用表单的方式,我们在本章中都会展开讨论:使用FormsModule以及使用ReactiveFormsModule。既然都要用到,那么这个模块就同时导入它们。因此需要在引用启动程序app.ts中这样写:
import {
FormsModule,
ReactiveFormsModule
} from '@angular/forms';
// farther down...
@NgModule({
declarations: [
FormsDemoApp,
DemoFormSku,
// ... our declarations here
],
imports: [
BrowserModule,
FormsModule, // <-- add this
ReactiveFormsModule // <-- and this
],
bootstrap: [ FormsDemoApp ]
})
class FormsDemoAppModule {}
这确保了我们能在视图中使用Angular表单指令。
FormsModule为我们提供了一些模板驱动的指令,例如:
template: `
Demo Form: Sku
`
我们导入了FormsModule,因此可以在视图中使用NgForm了。记住,当这些指令在视图中可用时,它就会被附加到任何能匹配其selector的节点上。
NgForm做了一件便利但隐晦的工作:它的选择器包含form 标签(而不用显式添加ngForm属性)。这意味着当我们导入FormsModule时候,NgForm就会被自动附加到视图中所有的
Angular提供了另一个指令,能让我们使用现有的FormGroup。它叫作formGroup,可以这样使用。
<h2 class="ui header">Demo Form: Sku with Builderh2>
<form [formGroup]="myForm"
这里我们告诉Angular,想用myForm作为这个表单的FormGroup。
注意:
当使用FormsModule时,NgForm会自动应用于
你也许不明白原因,这是因为NgForm的selector是:
form:not([ngNoForm]):not([formGroup]),ngForm,[ngForm]
这意味着你还可以使用 ngNoForm 属性产生一个不带NgForm的
将FormControl绑定到input标签上。记住,ngModel会创建一个新的FormControl对象,并附加到父FormGroup中。但在这个例子中,我们已经用FormBuilder创建了自己的FormControl。
要将现有的FormControl绑定到input上,可以用 formControl。
<label for="skuInput">SKUlabel>
<input type="text"
id="skuInput"
placeholder="SKU"
[formControl]="myForm.controls['sku']">
我 们 将 input标 签 上 的 formControl指 令 指 向 了 myForm.controls上 现 有 的FormControl控件sku。
完整代码
import { Component } from '@angular/core';
import {
FormBuilder,
FormGroup
} from '@angular/forms';
@Component({
selector: 'demo-form-sku-builder',
template: `
Demo Form: Sku with Builder
`
})
export class DemoFormSkuBuilder {
myForm: FormGroup;
constructor(fb: FormBuilder) {
this.myForm = fb.group({
'sku': ['ABC123']
});
}
onSubmit(value: string): void {
console.log('you submitted value: ', value);
}
}
想使用验证器,我们得做两件事:
(1) 为FormControl对象指定一个验证器;
(2) 在视图中检查验证器的状态,并据此采取行动。
要 为 FormControl对 象 分 配 一 个 验 证 器 , 我 们 可 以 直 接 把 它 作 为 第 二 个 参 数 传 给FormControl的构造函数。
let control = new FormControl('sku', Validators.required);
像这个例子中一样通过如下语法使用FormBuilder:
constructor(fb: FormBuilder) {
this.myForm = fb.group({
'sku': ['', Validators.required]
});
this.sku = this.myForm.controls['sku'];
}
到目前为止,我们只在提交表单时才调用onSubmit方法来获取表单的值。但我们也要经常监听控件的变化。
FormGroup和FormControl都带有EventEmitter(事件发射器),我们可以通过它来观察变化。
想监听控件的变化,我们要:
(1) 通过调用control.valueChanges访问到这个EventEmitter;
(2) 然后使用.subscribe方法添加一个监听器。
下面是一个例子。
constructor(fb: FormBuilder) {
this.myForm = fb.group({
'sku': ['', Validators.required]
});
this.sku = this.myForm.controls['sku'];
this.sku.valueChanges.subscribe(
(value: string) => {
console.log('sku changed to:', value);
}
);
this.myForm.valueChanges.subscribe(
(form: any) => {
console.log('form changed to:', form);
}
);
}
在这里我们监听了两个事件:sku字段的变化和整个表单的变化。
我们传递了一个带有next键的对象(也可以传递其他键,但目前还不用关心它们)。next就是我们希望当值发生变化时被调用的函数。
如果在输入框中输入kj,就会在控制台中看到:
sku changed to: k
form changed to: Object {sku: "k"}
sku changed to: kj
form changed to: Object {sku: "kj"}
如你所见,每一次按键都会触发控件的变化,我们的可观察对象也会被触发。监听单个FormControl时,我们会得到一个值(例如kj);而监听整个表单时,我们会得到一个包含键值对的对象(例如{sku: "kj"})。
ngModel是一个特殊的指令,它将模型绑定到表单中。ngModel的特殊之处在于它实现了 双向绑定 。
相对于单向绑定来说,双向绑定更加复杂和难以推断。Angular通常的数据流向是单向的:自顶向下。但对于表单来说,双向绑定有时会更容易。
下面对表单稍作修改:我们希望能输入产品名称productName。这次要用ngModel来保持组件实例和视图的同步。
我们的组件定义类如下所示。
export class DemoFormNgModel {
myForm: FormGroup;
productName: string;
constructor(fb: FormBuilder) {
this.myForm = fb.group({
'productName': ['', Validators.required]
});
}
onSubmit(value: string): void {
console.log('you submitted value: ', value);
}
}
注意,我们只是简单地将productName: string存成了实例变量。
紧接着,我们在input标签上使用ngModel。
<label for="productNameInput">Product Namelabel>
<input type="text"
id="productNameInput"
placeholder="Product Name"
[formControl]="myForm.get('productName')"
[(ngModel)]="productName">
注意,这里ngModel的语法很有意思:我们在ngModel属性上同时使用了()和[]。我们既使用了表示输入属性(@Input)的方括号[],又使用了表示输出属性(@Output)的圆括号(),这就是双向绑定的标志。
另 外 还 需 要 注 意 的 是 : 我 们 仍 然 用 formControl指 定 此 input应 该 绑 定 到 表 单 上 的FormControl。这是因为ngModel只负责将input绑定到对象实例上,但FormControl的功能是与此独立的。由于我们还需要对这个值加以验证并把它作为表单的一部分提交上去,仍要保留formControl指令。