文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

Angular 2 实现了全新的表单 API。这是一个非常新的内容,但是文档非常少。Gerard SansWassim Chegham 在 Google Hangout 上组织了一个 @angular_zone 分享活动,邀请 Angular 表单 API 的核心贡献者 Kara Erickson 。下面视频是这次分享的录像,本文是我们对这个分享视频的内容进行了整理。

视频地址(需要科学上网):https://youtu.be/E92KS_YCSf8

Angular 2 表单提供两种不同的方法:

  • 模板驱动, Angular 1 风格的表单
  • Reactive 或者叫模型驱动

这两种方法最终的效果是一样的,因此你可以根据个人喜好选择其中任何一种。但我们不建议混合使用,虽然二者共享相同的底层概念:

第 1 段(可获 1.5 积分)

视图

<input type="text" />

数据模型, 可用的属性如:

  • value
  • valid/invalid
  • pristine/dirty
  • touched/untouched
  • errors

这里很重要的需要注意的是通过 ngModel 或者类似的方法绑定的 “不是域模型” 。它是一个为保存表单验证以及控件状态属性的专属表单模型。

因此,视图部分称为 value accessor ,因为我们可能会有很多种不同类型的视图,提供不同的方法来访问其对应的值:

<input type="text" />
<input type="radio" />
<select></select>

value accessor 知道如何与底层的 DOM 元素交互来获取以及设置其对应的值。所有的这些都实现了 ControlValueAccessor 接口。

第 2 段(可获 1.25 积分)

model part 是 FormControl 类的实例。

每个表单控件都会有一个视图 (ControlValueAccessor) 和一个模型实例 (FormControl).

总结如下:

  • 模板驱动
    • 形如 ngModel, ngModelGroup, ngForm 的元素
    • 隐式的创建
    • 异步,很多东西都在通过 FormControl 实例在幕后进行处理
  • Reactive (数据模型驱动)
    • 形如 formControlName, formGroupName, formArrayName, formControl, formGroup 的元素
    • 显式的或者在代码中进行创建
    • 同步的而且通过程序创建的,没有其他额外的动作,而且过程中不牵扯到模板渲染
第 3 段(可获 0.83 积分)

让我们开始吧。

启用表单 API

在开始编码之前,我们需要在 Angular 2 应用中禁用过时的表单支持(disableDeprecatedForms()) 然后启用全新的表单 API (provideForms()) 。打开你用来启动应用的主文件:

// main entry point
import {bootstrap} from '@angular/platform-browser-dynamic';
import {disableDeprecatedForms, provideForms} from '@angular/forms';

import {App} from './app';

bootstrap(App, [
  disableDeprecatedForms(),
  provideForms()
  ])
  .catch(err => console.error(err));
第 4 段(可获 0.55 积分)

现在我们已经准备就绪。在 Plunker 上我们准备了一个可运行的例子,可以用来测试接下来我们将要讲到的各种不同概念。

Angular2 Forms Api demo - templated based approach

模板驱动方法

假设我们有一个简单的表单,如下所示:

<form>
  <div>
    Firstname: <input type="text" name="firstname" />
  </div>
  <div>
    Surname: <input type="text" name="surname" />
  </div>
  <div>
    Age: <input type="text" name="Age" />
  </div>
</form>

为了让这个表单支持 Angular 2 的 Forms API,我们需要通过 #form 为这个表单进行声明,如下所示:

<form #form="ngForm">
  ...
</form>
第 5 段(可获 0.71 积分)

简单绑定

下一步我们需要给表单增加 ngModel 属性:

<input type="text" ngModel name="firstname" />

这种方法是基于模板驱动的方法,通过添加 ngModel 属性,就会自动创为我们创建一个 FormContrl 实例,我们可以通过这个实例来访问底层的 DOM 元素值。为了了解这种双路绑定的方法是如何工作的,让我们来打印一下表单的状态:

<pre>{{ form.value | json }}</pre>

在文本框中输入任何内容就可以看到我们是如何设置一个属性的绑定的。详细代码请看 Plunker.

第 6 段(可获 1.13 积分)

对表单的控件进行分组

经常我们会对表单的一堆控件进行分组,就好比是一个地址信息包含了诸如街道、城市、省、邮编等等。所有的这些信息都在一个独立的表单,那么它们就可以用表单组来表示。

那么,有什么影响呢?主要是这些表单组可以继承表单中的所有属性,例如 value, validity, touched/untouched 等等,但是这些值却是其派生的表单组的值。例如,如果一个子表单无效,那么整个组都是无效的。

第 7 段(可获 1.24 积分)

此外,FormGroup 的字段会被序列化到一个专门的对象(其内容我们下面会介绍到)。其变量是 FormArray。关键不同的地方是其序列化后的数据是一个数组。当你不知道组里有多少个控件是就可以通过这个数组来获取,就好像是动态表单。

好了,到此为止?好。假设一个地址信息,我们可以使用如下代码来简单的添加更多的字段:

<form #form="ngForm">
  <div>
    Firstname: <input type="text" ngModel name="firstname" />
  </div>
  <div>
    Surname: <input type="text" ngModel name="surname" />
  </div>
  <div>
    Age: <input type="text" ngModel name="age" />
  </div>
  <fieldset>
    <legend>Address</legend>
    <div>
      Street: <input type="text" ngModel name="street" />
    </div>
    <div>
      ZipCode: <input type="text" ngModel name="zipCode" />
    </div>
  </fieldset>
</form>

序列化后的数据如下:

{
  "firstname": "",
  "surname": "",
  "age": "",
  "street": "",
  "zipCode": ""
}

但由于我们的用户界面上已经对地址进行了逻辑分组,我们想要让其序列化到一个独立的对象 address。如我们刚才所学的,只需要简单的添加一个 ngModelGroup 即可:

<form #form="ngForm">
  ...
  <fieldset ngModelGroup="address">
    <legend>Address</legend>
    <div>
      Street: <input type="text" ngModel name="street" />
    </div>
    <div>
      ZipCode: <input type="text" ngModel name="zipCode" />
    </div>
  </fieldset>
</form>
第 8 段(可获 1.48 积分)

由此产生的序列化对象如下所示:

{
  "firstname": "",
  "surname": "",
  "age": "",
  "address": {
    "street": "",
    "zipCode": ""
  }
}

Reactive 方法

我们已经了解了基于模板的方法。现在看看如何基于 Reactive 或者基于数据模型的方法来处理表单。同样也在 Plunker 上的例子:

Angular2 Forms Api demo - model based approach

由于这是基于数据模型的,因此我们从组件的 JavaScript 代码开始:

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

@Component({
  ...
})
export class App {
  form = new FormGroup({
    firstname: new FormControl(),
    surname: new FormControl(),
    age: new FormControl(),
    address: new FormGroup({
      street: new FormControl(),
      zipCode: new FormControl()
    })
  });
}
第 9 段(可获 0.66 积分)

注意我们是如何通过 FormControl 和 FormGroup 类来在程序中构建控制器和组的。接下来需要将表单和模板进行关联:

import {REACTIVE_FORM_DIRECTIVES} from '@angular/forms';

@Component({
  template: `
    <div>
      <h2>Forms Api demo</h2>
      <form [formGroup]="form">
        ...
      </form>
    </div>
  `,
  directives: [REACTIVE_FORM_DIRECTIVES]
})
export class App { ... }

我们使用 [formGroup] 绑定来实现。同时需要注意的是我们需要在组件的 directives 指令中增加 REACTIVE_FORM_DIRECTIVES 。

第 10 段(可获 0.58 积分)

最后我们需要使用 formControlName 和 formGroupName 来映射组合表单控制:

<form [formGroup]="form">
  <div>
    Firstname: <input type="text" formControlName="firstname" />
  </div>
  <div>
    Surname: <input type="text" formControlName="surname" />
  </div>
  <div>
    Age: <input type="text" formControlName="age" />
  </div>
  <fieldset formGroupName="address">
    <legend>Address</legend>
    <div>
      Street: <input type="text" formControlName="street" />
    </div>
    <div>
      ZipCode: <input type="text" formControlName="zipCode" />
    </div>
  </fieldset>
</form>

结束

棒极了,我希望这篇文章能让你对 Angular 2 的表单 API 有一个初步的认识。很明显 Angular 2 还有很多其他东西可以去探索的。

第 11 段(可获 0.58 积分)

文章评论

Gavin
一直关注angular,貌似很NB的样子
惠州访客
请问 怎么做表单验证?