表单多是WEB世界里最重要的,通过表单获得用户的输入.另外一方面,表单看起来又是简单的,你放置1个input标签,1个submit按钮,然后点击按钮,提交,有甚么难的呢?
实践证明,表单确切是很复杂的,理由以下:
- 表单输入意味着修改服务器与客户真个数据.
- 改变通常会需要去反应到其他的地方
- 用户会随便输入不同的值,所以需要校验
- 如果需要,需要清楚地标明毛病
- 依赖字段具有复杂的逻辑
- 不依托DOM选择器,我们不能测试表单
荣幸的是,angular2提供了解决这些问题的工具:
- Controls封装了输入,并且提供1个与它们工作的对象
- Validators给了我们1个校验工具
- Observers让我们可以检测我们的表单,并且根据需要作出反应
在这1章,我们会深入学习表单.
ng2中,表单的两个基础设施就是Controls 和 Control Groups
Controls代表1个简单的输入域,它是ng2中表单的最小单元.
Controls封装了字段的值和有效,修改,毛病的状态.
比如,我们会在Typescript中像下面1样使用Control
// create a new Control with the value "Nate"
let nameControl = new Control("Nate");
let name = nameControl.value; // -> Nate
// now we can query this control for certain values:
nameControl.errors // -> StringMap<string, any> of errors
nameControl.dirty // -> false
nameControl.valid // -> true
// etc.
为了构建表单,我们创建Control或Control group,然后添加元数据和逻辑给它们.
像angular中的其他事情1样,我们有1个类(在本例子中是Control),我们会作为属性添加给DOM(本例子中是ngControl),以下:
<!-- part of some bigger form -->
<input type="text" ngControl="name" />
在我们的表单上下文中新建1个Control对象.
许多表单都不止1个值,所以我们需要管理很多Controls,如果我们希望去校验我们的表单,去迭代每个Control并校验是笨重的.为了解决这个问题,ControlGroup提供了1个封装.
下面我们创建1个ControlGroup.
let personInfo = new ControlGroup({
firstName: new Control("Nate"),
lastName: new Control("Murray"),
zip: new Control("90210")
})
ControlGroup和Control有1个公共的父类(AbstractControl).也就是说我们可以像1个简单Control1样检查personInfo的值和状态.
personInfo.value; // -> {
// firstName: "Nate",
// lastName: "Murray",
// zip: "90210"
//}
// now we can query this control group for certain values, which have sensible
// values depending on the children Control's values:
personInfo.errors // -> StringMap<string, any> of errors
personInfo.dirty // -> false
personInfo.valid // -> true
// etc.
注意,当我们获得ControlGroup的值时,我们会获得到1个key-value的对象.
这里有许多创建表单的重要方面我们没有提及,下面我们来逐1学习.
下面这个是我们将要创建的1个简单的表单:
在我们的理解中,我们创建1个电子商务型的网站,我们会列出我们出售的产品列表.在这个例子中,我们需要存储产品的SKU码,因此,我们创建1个获得SKU码的简单表单,它仅仅只有1个输入域.
我们的表单是非常简单的,它只有1个input和1个sumbit按钮.
让我们将这个表单组装成1个组件,如果你没有忘记,创建组件的3个部份为:
- 配置
- 创建模板
- 实现组件类
code/forms/app/forms/demo_form_sku.ts
import { Component } from '@angular/core';
import { FORM_DIRECTIVES } from '@angular/common';
@Component({
selector: 'demo-form-sku',
directives: [FORM_DIRECTIVES],
注意,这里我们导入了FORM_DIRECTIVES,FORM_DIRECTIVES是1个指令组,它包括:
- ngControl
- ngControlGroup
- ngForm
- ngModel
- …
我们没有怎样使用这些指令,也没有说明它们能做甚么.但是,现在,只需要知道我们需要使用这些指令就能够了.
code/forms/app/ts/forms/demo_form_sku.ts
<div class="ui raised segment">
<h2 class="ui header">Demo Form: Sku</h2>
<form #f="ngForm" (ngSubmit)="onSubmit(f.value)" class="ui form">
<div class="field">
Forms in Angular 2 127
<label for="skuInput">SKU</label>
<input type="text" id="skuInput" placeholder="SKU" ngControl="sku">
<button type="submit" class="ui button">Submit</button>
</div>
</form>
</div>
注意,我们导入了FORM_DIRECTIVES,所以我们可使用ngForm添加到form上面.
NgForm做的事情是有好多好处的,而且也不明显.
导入FORM_DIRECTIVES后,ngForm会自动隐士地添加到任何的form标签上面.
ngForm给了我们两个东西:
- 1个命名为ngForm的ControlGroup
- 1个ngSubmit的输出
你可以在我们的代码中看到,我们使用了这两个东西:
<form #f="ngForm" (ngSubmit)="onSubmit(f.value)" class="ui form">
</form>
首先我们使用#f=”ngForm”创建了名为ngForm的表单,#v=thing说明,我们希望使用本地变量v代表这个view.
这里,我们创建了ngForm的标明,并绑定到变量f上.
我们的ngForm指令从哪里来? 它来自于NgForm指令.
ngForm的类型是甚么?它是ControlGroup.这意味着我们可以将我们的视图作为ControlGroup使用.(ngSubmit)代表的是我们提交的时候需要做的事情.
所有添加起来的理解就是,当我们提交表单的时候,传递ControlGroup的value作为参数,调用我们组建的onSubmit方法.
在我们讨论ngControl前,input标签有1些事情是我们感兴趣的.
code/forms/app/ts/forms/demo_form_sku.ts
<form #f="ngForm" (ngSubmit)="onSubmit(f.value)" class="ui form">
<div class="field">
Forms in Angular 2 127
<label for="skuInput">SKU</label>
<input type="text" id="skuInput" placeholder="SKU" ngControl="sku">
<button type="submit" class="ui button">Submit</button>
</div>
</form>
- class=”ui form”是可选的
- for属性与input的id是对应的
- placeholder是当用户没有输入的时候用来提示用的.
NgControl指令是用来标识ngControl这个选择器.这就意味着我们的input标签添加了这个属性:ngControl=”whatever”,这个例子中是sku.
NgControl会自动创建1个Control添加给父组件,这个例子中是ControlGroup.然后绑定这个DOM元素给Control.也就是说,我们通过名字sku将input与Control进行关联.
code/forms/app/ts/forms/demo_form_sku.ts
export class DemoFormSku { onSubmit(form: any): void {
console.log('you submitted value:', form);
}
}
将所有代码合起来,像下面这样:
code/forms/app/ts/forms/demo_form_sku.ts
import { Component } from '@angular/core';
import { FORM_DIRECTIVES } from '@angular/common';
@Component({
selector: 'demo-form-sku',
directives: [FORM_DIRECTIVES],
template: `
<div class="ui raised segment">
<h2 class="ui header">Demo Form: Sku</h2>
<form #f="ngForm"
(ngSubmit)="onSubmit(f.value)"
class="ui form">
<div class="field">
<label for="skuInput">SKU</label>
<input type="text"
id="skuInput"
placeholder="SKU"
ngControl="sku">
</div>
<button type="submit" class="ui button">Submit</button>
</form>
</div>
`
})
export class DemoFormSku {
onSubmit(form: any): void {
console.log('you submitted value:', form);
}
}
如果你运行这个程序,阅读器中会显示下面这样:
隐式使用ngForm和ngControl是方便的,但是它没有给予我们太多的可自定义选项,通常,我们会使用1个更加复杂的方式创建form,那就是FormBuilder.
FormBuilder是1个帮助类,帮助我们创建表单.你可以将其理解为工厂方法.
让我们将FormBuilder添加到我们前面的例子中,看下面:
- 在我们组件定义类中怎样使用FormBuilder
- 是view中怎样使用自定义的ControlGroup.
在我们的组件类构造器参数中注入1个参数.
code/forms/app/ts/forms/demo_form_sku_with_builder.ts
export class DemoFormSkuBuilder { myForm: ControlGroup;
constructor(fb: FormBuilder) {
this.myForm = fb.group({
'sku': ['ABC123'] });
}
onSubmit(value: string): void {
console.log('you submitted value: ', value);
}
注入以后,1个FormBuilder的实例将会被创建,并且我们将其分配给局部变量fb.
我们将会使用FormBuilder的两个主要方法:
- control:创建1个新的Control
- group: 创建1个新的ControlGroup
注意,我们在本例子中使用myForm局部变量代表我们的表单.
myForm的类型是ControlGroup.通过fb.group创建的.group的参数是1个key-value,它代表这个ControlGroup里面的Control.在这个例子中,我们设置了1个名字为sku的Control,它的值是ABC123.
现在,我们有1个myForm的ControlGroup,我们需要使用它(通过绑定它到我们的form元素).
我们希望改变去使用我们的myForm.如果没有忘记,在上面,我们说,导入FORM_DIRECTIVES时,ngForm会自动利用到我们的form元素上.我们也注意到了,ngForm创建了它自己的ControlGroup.好了,在这个例子中,我们不希望使用外部的ControlGroup,我们希望使用我们自己定义的myForm.我们应当怎样做?
当我们需要使用我们自己的ControlGroup时,angular提供了另外1种方式:它叫着ngFormModel,并且我们可以像下面这样使用它:
code/forms/app/ts/forms/demo_form_sku_with_builder.ts
<form [ngFormModel]="myForm"
(ngSubmit)="onSubmit(myForm.value)"
这里,我们使用ngFormModel告知angular,我们将会使用myForm到这个上.
我们需要使用myForm的onSubmit代替f的onSubmit,最后1件事情就是绑定我们的Control到input上,使用NgFormControl.
code/forms/app/ts/forms/demo_form_sku_with_builder.ts
<input
type="text"
id="skuInput"
placeholder="SKU"
[ngFormControl]="myForm.controls['sku']">
所有代码:
code/forms/app/ts/forms/demo_form_sku_with_builder.ts
import { Component } from '@angular/core';
import {
FORM_DIRECTIVES,
FormBuilder,
ControlGroup
} from '@angular/common';
@Component({
selector: 'demo-form-sku-builder',
directives: [FORM_DIRECTIVES],
template: `
<div class="ui raised segment">
<h2 class="ui header">Demo Form: Sku with Builder</h2>
<form [ngFormModel]="myForm"
(ngSubmit)="onSubmit(myForm.value)"
class="ui form">
<div class="field">
<label for="skuInput">SKU</label>
<input type="text"
id="skuInput"
placeholder="SKU"
[ngFormControl]="myForm.controls['sku']">
</div>
<button type="submit" class="ui button">Submit</button>
</form>
</div>
`
})
export class DemoFormSkuBuilder {
myForm: ControlGroup;
constructor(fb: FormBuilder) {
this.myForm = fb.group({
'sku': ['ABC123']
});
}
onSubmit(value: string): void {
console.log('you submitted value: ', value);
}
}
记住:
隐式创建ControlGroup和Control使用:
- ngForm
- ngControl
绑定存在的ControlGroup和Control使用:
- ngFormModel
- ngFormControl
用户常常不会按正确的模式输入正确的数据,如果用户没有按正确的格式输入数据,我们可以给用户1个反馈,并且让其不能提交,这类情况下,我们使用校验器(Validators).
Validators被Validators模块提供,而且,最简单的Validators是Validators.required.
Validators.require标明该Control是必填字段.
为了使用Validators,我们必须做两件事情.
- 分配1个Validator给Control
- 检查Validator状态,并给出反馈
let control = new Control('sku', Validators.required);
在我们的例子中,由于使用了FormBuilder,所以可以像下面这样:
code/forms/app/ts/forms/demo_form_with_validations_explicit.ts
constructor(fb: FormBuilder) {
this.myForm = fb.group({
'sku': ['', Validators.required]
现在,我们需要使用Validators到我们的view上,在View上,有两种方式访问Validators.
- 我们可以明确分配1个Control sku到我们类的实例,这个是比较啰嗦的,但是更加灵活.
- 在view中,我们从myForm获得Control sku,这中方式,在Component类中的代码量少,但是在view中的代码多.
为了分析他们的区分,我们看下面的例子.
以下:
更加灵活的方式是将Controls分开成单独的Control放在组件类中,下面是我们的类代码:
code/forms/app/ts/forms/demo_form_with_validations_explicit.ts
export class DemoFormWithValidationsExplicit {
myForm: ControlGroup;
sku: AbstractControl;
constructor(fb: FormBuilder) {
this.myForm = fb.group({
'sku': ['', Validators.required]
});
this.sku = this.myForm.controls['sku'];
}
onSubmit(value: string): void {
console.log('you submitted value: ', value);
}
}
注意:
- 我们在类中定义sku: AbstractControl
- 当使用FormBuilder创建以后,我们分配给sku.
这是1个很好的注意,它表明我们可以在我们的view的任何地方使用sku.现在,我们的sku是可以被validated.我们希望找到不同的方式去使用它.
- 校验所有的form,提供1条信息
- 分开校验每个Control,并提供1条信息
- 分开校验每个Control,当不合法的时候使用红色标注
- 分开校验每个Control,是不是已输入,如果没有输入,显示1条信息
我们可以通过检查myForm.valid来校验myForm.
code/forms/app/ts/forms/demo_form_with_validations_explicit.ts
<div *ngIf="!myForm.valid"
我们也能够在字段不符合规定的时候显示1条信息
code/forms/app/ts/forms/demo_form_with_validations_explicit.ts
[ngFormControl]="sku">
<div *ngIf="!sku.valid"
我们也能够标注字段的色彩,当不合法的时候.
code/forms/app/ts/forms/demo_form_with_validations_explicit.ts
<div class="field"
[class.error]="!sku.valid && sku.touched">
当需要依托特定的校验器时,也能够制定.通过hasError()来实现.
code/forms/app/ts/forms/demo_form_with_validations_explicit.ts
class="ui error message">SKU is invalid</div>
<div *ngIf="sku.hasError('required')"
code/forms/app/ts/forms/demo_form_with_validations_explicit.ts
/* tslint:disable:no-string-literal */
import { Component } from '@angular/core';
import {
CORE_DIRECTIVES,
FORM_DIRECTIVES,
FormBuilder,
ControlGroup,
Validators,
AbstractControl
} from '@angular/common';
@Component({
selector: 'demo-form-with-validations-explicit',
directives: [CORE_DIRECTIVES, FORM_DIRECTIVES],
template: `
<div class="ui raised segment">
<h2 class="ui header">Demo Form: with validations (explicit)</h2>
<form [ngFormModel]="myForm"
(ngSubmit)="onSubmit(myForm.value)"
class="ui form">
<div class="field"
[class.error]="!sku.valid && sku.touched">
<label for="skuInput">SKU</label>
<input type="text"
id="skuInput"
placeholder="SKU"
[ngFormControl]="sku">
<div *ngIf="!sku.valid"
class="ui error message">SKU is invalid</div>
<div *ngIf="sku.hasError('required')"
class="ui error message">SKU is required</div>
</div>
<div *ngIf="!myForm.valid"
class="ui error message">Form is invalid</div>
<button type="submit" class="ui button">Submit</button>
</form>
</div>
`
})
export class DemoFormWithValidationsExplicit {
myForm: ControlGroup;
sku: AbstractControl;
constructor(fb: FormBuilder) {
this.myForm = fb.group({
'sku': ['', Validators.required]
});
this.sku = this.myForm.controls['sku'];
}
onSubmit(value: string): void {
console.log('you submitted value: ', value);
}
}
像我们上面1样,我们在组件类中创建了1个实例变量存储每个input标签.
但是我们能不能不创建实例变量而直接阅读Control呢?答案是肯定的.我们会学习阅读Form的其他方式.
让我们看看另外的例子.
code/forms/app/ts/forms/demo_form_with_validations_shorthand.ts
export class DemoFormWithValidationsShorthand {
myForm: ControlGroup;
constructor(fb: FormBuilder) {
this.myForm = fb.group({
'sku': ['', Validators.required] });
}
onSubmit(value: any): void {
console.log('you submitted value:', value.sku);
}
}
这两个例子的代码有点像,但是仔细看,你会发现,sku: AbstractControl已不在这里面了.
让我们看看这3个字段级别的校验器,跟上面的作为对照.
由于我们没有直接定义1个本地变量保存sku援用,所以我们需要1种取得它援用的方式,这里有两种方式:
- 通过myForm.find
- 通过ngFormControl
myForm具有1个find函数,它可以通过路径找到它的子元素,然后校验他们.
code/forms/app/ts/forms/demo_form_with_validations_shorthand.ts
<div class="field"
[class.error]="!myForm.find('sku').valid && myForm.find('sku').touched">
这比前面的代码还多,不可取.
这里有另外1种方式取得援用,**通过ngForm导出ngFormControl.这是前面章节没有讲过的内容.
Component可以导出他们自己的援用,让你可以在视图中援用它们
我们在接下来的章节中讲授怎样使用exportAs导出,但是现在只需要知道,很多内建组件都可以像这样做.
在这个例子中,NgFormControl导出它自己作为ngForm.你可使用#reference来援用他们.
以下:
code/forms/app/ts/forms/demo_form_with_validations_shorthand.ts
<input
type="text"
id="skuInput"
placeholder="SKU"
#sku="ngForm"
[ngFormControl]="myForm.controls['sku']">
注意,上面的就是使用ngFormControl导出它自己引入变量sku,但是要注意,sku是1个指令,不是Control.为了访问sku Control,需要使用sku.control.
现在,sku对我们来讲是可以利用的.我们可以像下面这样使用它.
code/forms/app/ts/forms/demo_form_with_validations_shorthand.ts
<div *ngIf="!sku.control.valid"
class="ui error message">SKU is invalid</div>
<div *ngIf="sku.control.hasError('required')" class="ui error message">SKU is required</div>
当我们创建1个援用的时候,它是可以在其兄弟节点或子节点使用,但是不能在其父节点使用.
比如,不能像下面这样:
// this won't work
<div class="field"
[class.error]="!sku.control.valid && sku.control.touched">
由于div是form的父元素.
code/forms/app/ts/forms/demo_form_with_validations_shorthand.ts
import { Component } from '@angular/core';
import {
CORE_DIRECTIVES,
FORM_DIRECTIVES,
FormBuilder,
ControlGroup,
Validators
} from '@angular/common';
@Component({
selector: 'demo-form-with-validations-shorthand',
directives: [CORE_DIRECTIVES, FORM_DIRECTIVES],
template: `
<div class="ui raised segment">
<h2 class="ui header">Demo Form: with validations (shorthand)</h2>
<form [ngFormModel]="myForm"
(ngSubmit)="onSubmit(myForm.value)"
class="ui form">
<div class="field"
[class.error]="!myForm.find('sku').valid && myForm.find('sku').touched">
<label for="skuInput">SKU</label>
<input type="text"
id="skuInput"
placeholder="SKU"
#sku="ngForm"
[ngFormControl]="myForm.controls['sku']">
<div *ngIf="!sku.control.valid"
class="ui error message">SKU is invalid</div>
<div *ngIf="sku.control.hasError('required')"
class="ui error message">SKU is required</div>
</div>
<div *ngIf="!myForm.valid"
class="ui error message">Form is invalid</div>
<button type="submit" class="ui button">Submit</button>
</form>
</div>
`
})
export class DemoFormWithValidationsShorthand {
myForm: ControlGroup;
constructor(fb: FormBuilder) {
this.myForm = fb.group({
'sku': ['', Validators.required]
});
}
onSubmit(value: any): void {
console.log('you submitted value:', value.sku);
}
}
我们通常需要编写自己的校验器,怎样做?
为了弄清楚怎样实现自定义校验器,让我们看看内建的Validators.required.
export class Validators {
static required(c: Control): StringMap<string, boolean> {
return isBlank(c.value) || c.value == "" ? {"required": true} : null;
}
输入为1个Control,输出为1个
比如我们希望标志我们的sku,输入的必须是以123开头的.
我可以像下面这样写:
code/forms/app/ts/forms/demo_form_with_custom_validations.ts
function skuValidator(control: Control): { [s: string]: boolean } { if (!control.value.match(/^123/)) {
return {invalidSku: true};
}
}
当输入的不是以123开头时,这个校验器会返回invalidSku的毛病代码.
现在我们需要将skuValidator添加到Control上面去.但是这里有1个问题,我们的sku上面已有1个校验器了,我们怎样添加多个校验器到同1个Control呢?
答案是我们使用Validators.compose.以下:
code/forms/app/ts/forms/demo_form_with_custom_validations.ts
this.myForm = fb.group({
'sku': ['', Validators.compose([
Validators.required, skuValidator])]
});
Validators.compose包装多个校验器给Control,当所有的校验器都是有效的时候,Control才是有效的.否则校验不通过.
现在我们可以在视图中使用这个新的校验器.
code/forms/app/ts/forms/demo_form_with_custom_validations.ts
<div *ngIf="sku.hasError('invalidSku')"
class="ui error message">SKU must begin with <tt>123</tt></div>
到现在为止,当表单提交的时候,我们只是从我们的表单中读取数据.但是通常,我们希望监听每个Control的变化.
ControlGroup与Control都有1个EventEmitter,它可以用来监听数据的变化.
为了监听变化,我们需要做下面的两个操作:
- 通过调用control.valueChanges获得
- 使用EventEmitter.observer方法添加1个视察者
下面是1个例子:
code/forms/app/ts/forms/demo_form_with_events.ts
export class DemoFormWithEvents {
myForm: ControlGroup;
sku: AbstractControl;
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);
}
);
}
onSubmit(form: any): void {
console.log('you submitted value:', form.sku);
}
}
这里我们监听两个事件:sku变化事件和全部form的变化事件.
我们传递进去的是1个简单的key.接下来是1个函数,当值变哈的时候会被调用.
ngModel是1个特殊的指令,它绑定1个Model给form.ngModel是1个实现了双向数据绑定的特殊指令.双向数据绑定是比较复杂的.angular2默许使用的是单项数据流,但是在Form中,由于需要跟踪用户改变,所以使用双向数据绑定.
看下面的例子:
code/forms/app/ts/forms/demo_form_ng_model.ts
export class DemoFormNgModel {
myForm: ControlGroup; 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:
code/forms/app/ts/forms/demo_form_ng_model.ts
<input
type="text"
id="productNameInput"
placeholder="Product Name"
[ngFormControl]="myForm.find('productName')"
[(ngModel)]="productName">
ngModel的语法是很奇特的,它使用[]和()合起来,由前面的可知,[]是输入,()是输出,也就是说双向就是输入和输出都绑定了.
然后,我们将productName显示出来.
code/forms/app/ts/forms/demo_form_ng_model.ts
<div class="ui info message">
The product name is: {{productName}}
</div>
看起来像下面这样:
可以看到,是实时同步的.
Form有很多的复杂功能,但是angular2提供了1个清晰明了的方式,1旦你学会使用了ControlGroups, Controls, 和 Validations,使用表单就会变得很简单了.
上一篇 给你一台美国电视......
下一篇 Git学习笔记整理