该系列用于探索一些 Angular 中灵活或者新的用法。本文简单介绍模型驱动和模板驱动的表单,以及一些表单验证。

# Angular2 表单

# 模板驱动

很多时候我们都会使用模板驱动的表单,因为很方便。 使用 Angular 模板语法编写模板,便是模板驱动的表单。

  1. FormModule

使用模板驱动的一些模板语法,我们需要引入FormModule:

import { FormsModule } from "@angular/forms";

这样,我们的<form><input>等表单元素便是 Angular 元素,拥有了像ngModelngSubmit等指令或属性。

  1. 模板驱动的表单

这里我们使用最简单的input,需要输入一个 3-8 字符的名字。Component 中的 js 代码几乎没多少,而 html 模板如下:

<form class="form-horizontal" #form="ngForm" (ngSubmit)="submit()">
  <div class="form-group">
    <label class="col-md-2 control-label">name</label>
    <div class="col-md-6">
      <input
        class="form-control"
        type="text"
        [(ngModel)]="model.name"
        name="name"
        required
        maxlength="8"
        minlength="3"
        #name="ngModel"
      />
    </div>
  </div>
  <div
    class="alert alert-danger col-md-6 col-md-offset-2"
    *ngIf="name.errors && (name.dirty || name.touched)"
  >
    <p [hidden]="!name.errors.required">必填</p>
    <p [hidden]="!name.errors.minlength">不小于3个字符</p>
    <p [hidden]="!name.errors.maxlength">不大于8个字符</p>
  </div>
  <div class="form-group">
    <div class="col-sm-offset-2 col-sm-10">
      <button [disabled]="!form.valid" type="submit" class="btn btn-info">
        提交
      </button>
    </div>
  </div>
</form>
  • 通过ngModel跟踪修改状态与有效性验证

在表单中使用ngModel可以获得比仅使用双向数据绑定更多的控制权,不仅仅跟踪状态。它还使用特定的 Angular CSS 类来更新控件,以反映当前状态。 可以利用这些 CSS 类来修改控件的外观,显示或隐藏消息。

  • required/maxlength/minlength: 用于表单校验
    • name.touched: 控件被访问过
    • name.dirty: 控件的值变化了
    • name.errors: 控件校验错误

指向input控件的引用变量上的valid属性,可用于检查控件是否有效、是否显示/隐藏错误信息。

  • #name="ngModel"

#name是模板引用变量。使用井号(#)来声明引用变量。 模板引用变量通常用来引用模板中的某个 DOM 元素,它还可以引用 Angular 组件或指令或 Web Component。

指令的exportAs属性告诉 Angular 如何链接模板引用变量到指令。 这里把name设置为ngModel是因为ngModel指令的exportAs属性设置成了"ngModel"。 name属性的用途是有效性验证和对表单元素的变更进行追踪。

  • 使用ngSubmit提交该表单

按钮位于表单的底部,它自己不做任何事,但因为有type="submit",所以会触发表单提交。 而要使得表单提交生效,需要把该表单的ngSubmit事件属性绑定到对应的提交事件。

如果需要校验,则需要添加#form="ngForm",将form变量exportAs属性设置成ngForm,可跟踪表单有效性验证:

<form class="form-horizontal" #form="ngForm" (ngSubmit)="submit()">
  <button [disabled]="!form.valid" type="submit" class="btn btn-info">
    提交
  </button>
</form>

# 模型驱动

模型驱动表单又称为响应式表单,它将在 html 上的一些模板语法迁移到 js 中使用。 Angular 的响应式表单能让实现响应式编程风格更容易,这种编程风格更倾向于在非 UI 的数据模型(通常接收自服务器)之间显式的管理数据流,并且用一个 UI 导向的表单模型来保存屏幕上 HTML 控件的状态和值。响应式表单可以让使用响应式编程模式、测试和校验变得更容易。

简单地说,就是模型驱动表单更加灵活,值和状态的同步更新,以及测试性较好。

  1. 异步 vs. 同步

响应式表单是同步的。模板驱动表单是异步的。

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

  1. ReactiveFormsModule

使用模型驱动的一些模板语法,我们需要引入ReactiveFormsModule:

import { ReactiveFormsModule } from "@angular/forms";
  1. 基础的表单类
  • AbstractControl
    • 是三个具体表单类的抽象基类
    • 为它们提供了一些共同的行为和属性,其中有些是可观察对象(Observable)。
  • FormControl
    • 用于跟踪一个单独的表单控件的值和有效性状态
    • 它对应于一个 HTML 表单控件,比如输入框和下拉框
  • FormGroup
    • 用于跟踪一组AbstractControl的实例的值和有效性状态
    • 该组的属性中包含了它的子控件
    • 组件中的顶级表单就是一个FormGroup
  • FormArray
    • 用于跟踪AbstractControl实例组成的有序数组的值和有效性状态
  1. FormGroup

有多个FormControl,把它们注册进一个父FormGroup中:

reactiveForm = new FormGroup({
  name: new FormControl(),
  email: new FormControl()
});

formGroup关联到的是form元素上的FormGroup实例 reactiveForm:

<form
  class="form-horizontal"
  [formGroup]="reactiveForm"
  (ngSubmit)="submit()"
></form>

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

  1. formControlName

有了FormGroupname输入框就需要再添加一个语法formControlName="name",以便让它关联到类中正确的FormControl上:

<div class="form-group">
  <label class="col-md-2 control-label">name</label>
  <div class="col-md-6">
    <input class="form-control" formControlName="name" />
  </div>
</div>

这个语法告诉 Angular,查阅父FormGroup,然后在这个FormGroup中查阅一个名叫nameFormControl

  1. FormBuilder

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

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

    createForm() {
        this.reactiveForm = this.fb.group({
            name: ['', [
                Validators.required,
                Validators.minLength(3),
                Validators.maxLength(8)
            ]
            ],
            email: ['',
                [
                    Validators.required,
                    Validators.email
                ]
            ],
        });
    }

FormBuilder.group是一个用来创建FormGroup的工厂方法,它接受一个对象,对象的键和值分别是FormControl的名字和它的定义。 每个FormControl的设置都是FormControl名字和数组值。第一个数组元素是控件对应的当前值,第二个值(可选)是验证器函数或者验证器函数数组。

大多数验证器函数是 Angular 以Validators类的静态方法的形式提供的原装验证器。

  1. valueChanges

可以通过订阅表单控件的属性之一来了解表单控件变化。 valueChanges返回一个 RxJS 的 Observable 对象。

this.reactiveForm.valueChanges.subscribe(data => this.onValueChanged(data));

这里我们需要添加触发事件回调:

    // 定义表单错误
    formErrors = {
        name: ''
    };
    // 对表单错误做说明映射
    validationMessages = {
        name: {
            required: '名字必填',
            minlength: '名字最少3个字符',
            maxlength: '名字最多8个字符'
        }
    };
    // 表单更新触发回调
    onValueChanged(data?: any) {
        if (!this.reactiveForm) {
            return;
        }
        const form = this.reactiveForm;
        // 遍历表单控件名字
        for (const field in this.formErrors) {
            this.formErrors[field] = ''; // 清除之前的错误信息
            const control = form.get(field);  // 获取控件
            if (control && control.dirty && !control.valid) {
                // 遍历获取错误说明
                const messages = this.validationMessages[field];
                for (const key in control.errors) {
                    this.formErrors[field] += messages[key] + ' ';
                }
            }
        }
    }

最终效果图:

image

# 结束语

讲到这里,我们的两种 Angular 表单--模型驱动和模板驱动,它们的简单使用和校验已经完成。
而需要更复杂一些的功能,像多级FormGroup、动态表单等,后面我们或许也会讲到。
此处查看项目代码 (opens new window)
此处查看页面效果 (opens new window)

部分文章中使用了一些网站的截图,如果涉及侵权,请告诉我删一下谢谢~
温馨提示喵