Angular中的自定义异步验证器

在实际工作中,我们经常需要一个基于后端API验证值的验证器。为此,Angular提供了一种定义自定义异步验证器的简便方法。

本文将介绍如何为Angular应用程序创建自定义异步验证器。

通常你会调用一个真正的后端,但是在这里我们将创建一个虚拟的JSON文件,我们可以通过使用Http服务来调用它。如果正在使用Angular CLI,则可以将JSON文件放在/assets文件夹中,它将自动可用;

/assets/users.json

[
  { "name": "Paul", "email": "paul@example.com" },
  { "name": "Ringo", "email": "ringo@example.com" },
  { "name": "John", "email": "john@example.com" },
  { "name": "George", "email": "george@example.com" }
]

注册服务

接下来,让我们创建一个具有checkEmailNotTaken方法的服务,该方法触发对我们的JSON文件的http GET调用。这里我们使用RxJS的延迟运算符来模拟一些延迟:

signup.service.ts

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/delay';

@Injectable()
export class SignupService {
  constructor(private http: Http) {}

  checkEmailNotTaken(email: string) {
    return this.http
      .get('assets/users.json')
      .delay(1000)
      .map(res => res.json())
      .map(users => users.filter(user => user.email === email))
      .map(users => !users.length);
  }
}

请注意我们如何筛选与提供给方法的用户具有相同电子邮件的用户。然后我们再次映射结果并进行测试以确保我们得到一个空置对象。

在真实场景中,您可能还想使用debounceTime和distinctUntilChanged运算符的组合,如我们在创建实时搜索的帖子中所讨论的。引入一些这样的去抖动将有助于将发送到后端API的请求数量保持在最低水平。

组件和异步验证器

我们的简单组件初始化我们的反应形式并定义我们的异步验证器:validateEmailNotTaken。请注意我们的FormBuilder.group声明中的表单控件如何将异步验证器作为第三个参数。这里我们只使用一个异步验证器,但是你想在数组中包含多个异步验证器:

app.component.ts

import { Component, OnInit } from '@angular/core';
import {
  FormBuilder,
  FormGroup,
  Validators,
  AbstractControl
} from '@angular/forms';

import { SignupService } from './signup.service';

@Component({ ... })
export class AppComponent implements OnInit {
  myForm: FormGroup;

  constructor(
    private fb: FormBuilder,
    private signupService: SignupService
  ) {}

  ngOnInit() {
    this.myForm = this.fb.group({
      name: ['', Validators.required],
      email: [
        '',
        [Validators.required, Validators.email],
        this.validateEmailNotTaken.bind(this)
      ]
    });
  }

  validateEmailNotTaken(control: AbstractControl) {
    return this.signupService.checkEmailNotTaken(control.value).map(res => {
      return res ? null : { emailTaken: true };
    });
  }
}

我们的验证器与典型的自定义验证器非常相似。这里我们直接在组件类中定义了验证器而不是单独的文件。这样可以更轻松地访问我们注入的服务实例。另请注意我们如何绑定值以确保它指向组件类。

我们还可以在自己的文件中定义我们的异步验证器,以便更容易地重用和分离关注点。唯一棘手的部分是找到一种方法来提供我们的服务实例。在这里,例如,我们创建一个具有createValidator静态方法的类,该方法接收我们的服务实例并返回我们的验证器函数:

/validators/async-email.validator.ts

import { AbstractControl } from '@angular/forms';
import { SignupService } from '../signup.service';

export class ValidateEmailNotTaken {
  static createValidator(signupService: SignupService) {
    return (control: AbstractControl) => {
      return signupService.checkEmailNotTaken(control.value).map(res => {
        return res ? null : { emailTaken: true };
      });
    };
  }
}

然后,回到我们的组件中,我们导入ValidateEmailNotTaken类,我们可以使用这样的验证器:

ngOnInit() {
  this.myForm = this.fb.group({
    name: ['', Validators.required],
    email: [
      '',
      [Validators.required, Validators.email],
      ValidateEmailNotTaken.createValidator(this.signupService)
    ]
  });
}

模板

在模板中,事情真的很简单:

app.component.html

<form [formGroup]="myForm">
  <input type="text" formControlName="name">
  <input type="email" formControlName="email">

  <div *ngIf="myForm.get('email').status === 'PENDING'">
    Checking...
  </div>
  <div *ngIf="myForm.get('email').status === 'VALID'">
    😺 Email is available!
  </div>

  <div *ngIf="myForm.get('email').errors && myForm.get('email').errors.emailTaken">
    😢 Oh noes, this email is already taken!
  </div>
</form>

您可以看到我们根据电子邮件表单控件上status属性的值显示不同的消息。对于可能的值状态VALIDINVALIDPENDING禁用。如果异步验证错误输出我们的emailTaken错误,我们也会显示错误消息。

使用异步验证器验证的表单字段在验证待处理时也将具有ng-pending类。这样可以轻松设置当前待验证字段的样式。

✨你有它!使用后端API检查有效性的简便方法。