程序员人生 网站导航

第五章 表单

栏目:htmlcss时间:2016-06-27 08:26:38

表单是最重要也是最复杂的

表单多是WEB世界里最重要的,通过表单获得用户的输入.另外一方面,表单看起来又是简单的,你放置1个input标签,1个submit按钮,然后点击按钮,提交,有甚么难的呢?
实践证明,表单确切是很复杂的,理由以下:

  • 表单输入意味着修改服务器与客户真个数据.
  • 改变通常会需要去反应到其他的地方
  • 用户会随便输入不同的值,所以需要校验
  • 如果需要,需要清楚地标明毛病
  • 依赖字段具有复杂的逻辑
  • 不依托DOM选择器,我们不能测试表单

荣幸的是,angular2提供了解决这些问题的工具:

  • Controls封装了输入,并且提供1个与它们工作的对象
  • Validators给了我们1个校验工具
  • Observers让我们可以检测我们的表单,并且根据需要作出反应

在这1章,我们会深入学习表单.

Controls 和 Control Groups

ng2中,表单的两个基础设施就是Controls 和 Control Groups

Controls

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对象.

ControlGroup

许多表单都不止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个简单的表单:

输入图片说明

在我们的理解中,我们创建1个电子商务型的网站,我们会列出我们出售的产品列表.在这个例子中,我们需要存储产品的SKU码,因此,我们创建1个获得SKU码的简单表单,它仅仅只有1个输入域.

我们的表单是非常简单的,它只有1个input和1个sumbit按钮.

让我们将这个表单组装成1个组件,如果你没有忘记,创建组件的3个部份为:

  • 配置
  • 创建模板
  • 实现组件类

Simple SKU,配置选项

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

我们没有怎样使用这些指令,也没有说明它们能做甚么.但是,现在,只需要知道我们需要使用这些指令就能够了.

Simple SKU模板

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 和 NgForm

注意,我们导入了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方法.

input和ngControl

在我们讨论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进行关联.

Simple SKU Form:组件定义

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

如果你运行这个程序,阅读器中会显示下面这样:

输入图片说明

使用FormBuilder

隐式使用ngForm和ngControl是方便的,但是它没有给予我们太多的可自定义选项,通常,我们会使用1个更加复杂的方式创建form,那就是FormBuilder.

FormBuilder是1个帮助类,帮助我们创建表单.你可以将其理解为工厂方法.

让我们将FormBuilder添加到我们前面的例子中,看下面:

  • 在我们组件定义类中怎样使用FormBuilder
  • 是view中怎样使用自定义的ControlGroup.

怎样使用FormBuilder

在我们的组件类构造器参数中注入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元素).

在view中使用myForm

我们希望改变去使用我们的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中的代码多.

为了分析他们的区分,我们看下面的例子.

显示的将Control变量设置为实例变量

以下:

输入图片说明

更加灵活的方式是将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); } }

显示设置sku Controls作为组件类的实例变量

像我们上面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个本地变量保存sku援用,所以我们需要1种取得它援用的方式,这里有两种方式:

  • 通过myForm.find
  • 通过ngFormControl

myForm.find

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">

这比前面的代码还多,不可取.

通过NgFormControl导出

这里有另外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>

sku变量的范围

当我们创建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个

编写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的毛病代码.

分配校验器给Control

现在我们需要将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

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,使用表单就会变得很简单了.

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐