Angular 响应式/模型驱动的表单

在我们的模板中使用指令给我们没有太多的样板快速原型的能力,我们被束缚住了。 相反,响应式表单,让我们通过代码定义我们的形式,并给我们对数据验证更多的灵活性和控制。

首先,在它的简便性上有一点魔法,但是在你熟悉基础知识之后,学习它的构建块将允许你处理更复杂的用例。

响应式表单基础

开始使用FormBuilder,我们必须首先确保我们正在使用正确的指令和正确的类,以利用程序式的表单。 为此,我们需要确保在应用程序模块的引导阶段中导入FormsModuleReactiveFormsModule

这将让我们访问组件,指令和providers,如FormBuilderFormGroupFormControl

在我们的例子中,将构建一个登录表单,我们看下面的内容:

app/login-form.component.ts

import { Component } from '@angular/core';
import { FormGroup, FormControl, FormBuilder } from '@angular/forms';
@Component({
  selector: 'app-root',
  templateUrl: 'app/app.component.html'
})
export class AppComponent {
  username = new FormControl('')
  password = new FormControl('')
  loginForm: FormGroup = this.builder.group({
    username: this.username,
    password: this.password
  });
  constructor(private builder: FormBuilder) { }
  login() {
    console.log(this.loginForm.value);
    // Attempt Logging in...
  }
}

app/login-form.component.html

<form [formGroup]="loginForm" (ngSubmit)="login()">
  <label for="username">username</label>
  <input type="text" name="username" id="username" [formControl]="username">
  <label for="password">password</label>
  <input type="password" name="password" id="password" [formControl]="password">
  <button type="submit">log in</button>
</form>

View Example

FormControl

请注意,FormControl类被分配到类似命名的字段,无论是在此还是在FormBuilder#group({ })方法中。 这主要是为了方便访问。 通过保存thisFormControl实例的引用,您可以访问模板中的输入,而无需引用表单本身。 否则,可以通过使用loginForm.controls.usernameloginForm.controls.password在模板中访问表单字段。 同样,此情况下的任何FormControl实例都可以通过使用其.root属性(例如username.root.controls.password)访问其父组。

确保root和控件在使用之前存在。

FormControl需要两个属性:初始值和验证器列表。 现在,我们没有验证。 这将在后续步骤中添加。

验证响应式表单

从上一个登录表单构建,我们可以快速轻松地添加验证。

Angular 2提供了开箱即用的许多验证器。 它们可以与其余的依赖关系一起导入。

app/login-form.component.ts

import { Component } from '@angular/core';
import { Validators, FormBuilder, FormControl } from '@angular/forms';
@Component({
  // ...
})
export class AppComponent {
  username = new FormControl('', [
    Validators.required,
    Validators.minLength(5)
  ]);
  password = new FormControl('', [Validators.required]);
  loginForm: FormGroup = this.builder.group({
    username: this.username,
    password: this.password
  });
  constructor(private builder: FormBuilder) { }
  login () {
    console.log(this.loginForm.value);
    // Attempt Logging in...
  }
}

app/login-form.component.html

<form [formGroup]="loginForm" (ngSubmit)="login()">
  <div>
    <label for="username">username</label>
    <input
      type="text"
      name="username"
      id="username"
      [formControl]="username">
    <div [hidden]="username.valid || username.untouched">
      <div>
        The following problems have been found with the username:
      </div>
      <div [hidden]="!username.hasError('minlength')">
        Username can not be shorter than 5 characters.
      </div>
      <div [hidden]="!username.hasError('required')">
        Username is required.
      </div>
    </div>
  </div>
  <div >
    <label for="password">password</label>
    <input
      type="password"
      name="password"
      id="password" [formControl]="password">
    <div [hidden]="password.valid || password.untouched">
      <div>
        The following problems have been found with the password:
      </div>
      <div [hidden]="!password.hasError('required')">
        The password is required.
      </div>
    </div>
  </div>
  <button type="submit" [disabled]="!loginForm.valid">Log In</button>
</form>

注意,我们对字段和表单本身添加了相当稳健的验证,只使用内置验证器和一些模板逻辑。

View Example

我们使用.valid.untouched来确定是否需要显示错误,而字段是必需的,没有理由告诉用户如果字段尚未被访问,该值是错误的。

对于内置验证,我们在表单元素上调用.hasError(),我们传递一个字符串,它表示我们包含的验证器函数。 仅当此测试返回true时,才会显示错误消息。

自定义验证响应式表单

内置验证器非常有用,能够引入你自己的验证方式。 Angular 2可以让你以很轻松地做到。

让我们假设使用相同的登录表单,但现在我们还要测试我们的密码在其中某处有感叹号。

app/login-form.component.ts

function hasExclamationMark(input: FormControl) {
  const hasExclamation = input.value.indexOf('!') >= 0;
  return hasExclamation ? null : { needsExclamation: true };
}
// ...
this.password = new FormControl('', [
  Validators.required,
  hasExclamationMark
]);

一个简单的函数接受FormControl实例,并返回null如果一切都很好。 如果测试失败,它返回一个具有任意命名属性的对象。 属性名称将用于.hasError()测试。

app/login-form.component.ts

<!-- ... -->
<div [hidden]="!password.hasError('needsExclamation')">
  Your password must have an exclamation mark!
</div>
<!-- ... -->

View Example

预定义参数

有一个自定义验证器来检查感叹号可能是有帮助的,但如果你需要检查一些其他形式的标点符号怎么办? 你可能需要一遍又一遍地写做相同的事情。

考虑前面的例子Validators.minLength(5)。 如果验证器只是一个函数,它们如何允许一个参数来控制长度? 简单,真的。 这不是Angular或TypeScript的一个戏法,它是简单的JavaScript闭包。

function minLength(minimum) {
  return function(input) {
    return input.value.length >= minimum ? null : { minLength: true };
  };
}

假设你有一个函数,它接受一个“最小”参数并返回另一个函数。 从内部定义和返回的函数成为验证器。 闭包引用允许您记住最终调用验证器时的最小值。

让我们将这种思路应用到PunctuationValidator

app/login-form.component.ts

function hasPunctuation(punctuation: string, errorType: string) {
  return function(input: FormControl) {
    return input.value.indexOf(punctuation) >= 0 ?
        null :
        { [errorType]: true };
  };
}
// ...
this.password = new FormControl('', [
  Validators.required,
  hasPunctuation('&', 'ampersandRequired')
]);

app/login-form.component.html

<!-- ... -->
<div [hidden]="!password.hasError('ampersandRequired')">
  You must have an & in your password.
</div>
<!-- ... -->

View Example

使用其他输入验证输入

记住前面提到的:输入可以通过.root访问他们的父上下文。 因此,复杂的验证可以通过.root在表单上获取。

function duplicatePassword(input: FormControl) {
  if (!input.root || !input.root.controls) {
    return null;
  }
  const exactMatch = input.root.controls.password === input.value;
  return exactMatch ? null : { mismatchedPassword: true };
}
// ...
this.duplicatePassword = new FormControl('', [
  Validators.required,
  duplicatePassword
]);

View Example

下一节:Angular模块提供了一种用于创建可以组合以构建应用程序的功能块的机制。