一、引言

在开发 Web 应用时,表单验证是必不可少的一部分。尤其是在处理复杂业务时,简单的内置验证规则往往满足不了需求。Angular 作为一款强大的前端框架,提供了自定义验证器的功能,能让我们构建出复杂的表单验证逻辑。下面,咱们就来详细了解一下如何使用 Angular 自定义验证器。

二、Angular 表单验证基础

2.1 模板驱动表单验证

模板驱动表单是 Angular 中比较简单的一种表单处理方式。它主要依赖于 HTML 模板中的指令来实现验证。比如我们要创建一个简单的登录表单,要求用户名和密码不能为空。

// 技术栈:Angular
import { Component } from '@angular/core';

@Component({
  selector: 'app-login-form',
  template: `
    <form #loginForm="ngForm">
      <!-- 用户名输入框,添加 required 指令进行必填验证 -->
      <input type="text" name="username" ngModel required>
      <!-- 显示用户名验证错误信息 -->
      <div *ngIf="loginForm.controls.username?.invalid && (loginForm.controls.username?.dirty || loginForm.controls.username?.touched)">
        用户名是必填项
      </div>
      <input type="password" name="password" ngModel required>
      <div *ngIf="loginForm.controls.password?.invalid && (loginForm.controls.password?.dirty || loginForm.controls.password?.touched)">
        密码是必填项
      </div>
      <button type="submit" [disabled]="loginForm.invalid">登录</button>
    </form>
  `
})
export class LoginFormComponent { }

在这个示例中,我们使用了 required 指令来验证用户名和密码是否为空。当用户输入不符合要求时,会显示相应的错误信息。

2.2 响应式表单验证

响应式表单是 Angular 中更灵活的表单处理方式,它通过在组件类中定义表单控件和验证规则。下面是一个简单的响应式表单示例,同样是登录表单。

// 技术栈:Angular
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';

@Component({
  selector: 'app-login-form-reactive',
  template: `
    <form [formGroup]="loginForm">
      <!-- 绑定用户名表单控件 -->
      <input type="text" formControlName="username">
      <!-- 显示用户名验证错误信息 -->
      <div *ngIf="loginForm.get('username')?.invalid && (loginForm.get('username')?.dirty || loginForm.get('username')?.touched)">
        用户名是必填项
      </div>
      <input type="password" formControlName="password">
      <div *ngIf="loginForm.get('password')?.invalid && (loginForm.get('password')?.dirty || loginForm.get('password')?.touched)">
        密码是必填项
      </div>
      <button type="submit" [disabled]="loginForm.invalid">登录</button>
    </form>
  `
})
export class LoginFormReactiveComponent {
  // 创建表单组,包含用户名和密码表单控件,并添加必填验证规则
  loginForm = new FormGroup({
    username: new FormControl('', Validators.required),
    password: new FormControl('', Validators.required)
  });
}

在这个示例中,我们使用 FormGroupFormControl 来创建表单,并通过 Validators.required 来添加必填验证规则。

三、自定义验证器的创建

3.1 同步自定义验证器

同步自定义验证器是指在验证时不需要异步操作,能立即返回验证结果。比如我们要创建一个验证用户名长度的自定义验证器。

// 技术栈:Angular
import { AbstractControl, ValidatorFn } from '@angular/forms';

// 定义一个函数,返回一个验证器函数
export function usernameLengthValidator(minLength: number, maxLength: number): ValidatorFn {
  return (control: AbstractControl): { [key: string]: any } | null => {
    const value = control.value;
    if (value && (value.length < minLength || value.length > maxLength)) {
      // 验证不通过,返回错误信息
      return { 'usernameLength': true };
    }
    // 验证通过,返回 null
    return null;
  };
}

在这个示例中,我们定义了一个 usernameLengthValidator 函数,它接受最小长度和最大长度作为参数,返回一个验证器函数。验证器函数会检查输入的用户名长度是否在指定范围内,如果不在则返回一个包含错误信息的对象,否则返回 null

3.2 异步自定义验证器

异步自定义验证器通常用于需要异步操作的验证,比如检查用户名是否已存在。下面是一个简单的异步自定义验证器示例。

// 技术栈:Angular
import { AbstractControl, AsyncValidatorFn, ValidationErrors, Observable, of } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { delay } from 'rxjs/operators';

// 定义一个异步验证器函数
export function usernameExistsValidator(http: HttpClient): AsyncValidatorFn {
  return (control: AbstractControl): Observable<ValidationErrors | null> => {
    const username = control.value;
    // 模拟一个异步请求,检查用户名是否存在
    return http.get<{ exists: boolean }>(`/api/check-username?username=${username}`)
    .pipe(
      delay(1000), // 模拟延迟
      map(response => {
        if (response.exists) {
          // 用户名已存在,返回错误信息
          return { 'usernameExists': true };
        }
        // 用户名不存在,验证通过
        return null;
      })
    );
  };
}

在这个示例中,我们定义了一个 usernameExistsValidator 函数,它接受一个 HttpClient 对象作为参数,返回一个异步验证器函数。异步验证器函数会发送一个 HTTP 请求来检查用户名是否已存在,根据响应结果返回相应的验证结果。

四、自定义验证器的应用

4.1 在模板驱动表单中应用自定义验证器

我们可以在模板驱动表单中使用自定义验证器。下面是一个使用用户名长度自定义验证器的示例。

// 技术栈:Angular
import { Component } from '@angular/core';
import { usernameLengthValidator } from './username-length-validator';

@Component({
  selector: 'app-template-form',
  template: `
    <form #templateForm="ngForm">
      <!-- 使用自定义验证器 -->
      <input type="text" name="username" ngModel #username="ngModel" [ngModelOptions]="{ updateOn: 'blur' }" [ngClass]="{ 'is-invalid': username.invalid && (username.dirty || username.touched) }" [appUsernameLength]="{ minLength: 3, maxLength: 10 }">
      <!-- 显示验证错误信息 -->
      <div *ngIf="username.invalid && (username.dirty || username.touched)">
        <div *ngIf="username.errors?.usernameLength">
          用户名长度必须在 3 到 10 个字符之间
        </div>
      </div>
      <button type="submit" [disabled]="templateForm.invalid">提交</button>
    </form>
  `
})
export class TemplateFormComponent { }

在这个示例中,我们在输入框上使用了自定义验证器 appUsernameLength,并传入了最小长度和最大长度参数。当验证不通过时,会显示相应的错误信息。

4.2 在响应式表单中应用自定义验证器

在响应式表单中应用自定义验证器也很简单。下面是一个使用用户名长度和用户名存在验证器的示例。

// 技术栈:Angular
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { usernameLengthValidator } from './username-length-validator';
import { usernameExistsValidator } from './username-exists-validator';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-reactive-form',
  template: `
    <form [formGroup]="reactiveForm">
      <input type="text" formControlName="username">
      <!-- 显示验证错误信息 -->
      <div *ngIf="reactiveForm.get('username')?.invalid && (reactiveForm.get('username')?.dirty || reactiveForm.get('username')?.touched)">
        <div *ngIf="reactiveForm.get('username')?.errors?.usernameLength">
          用户名长度必须在 3 到 10 个字符之间
        </div>
        <div *ngIf="reactiveForm.get('username')?.errors?.usernameExists">
          用户名已存在
        </div>
      </div>
      <button type="submit" [disabled]="reactiveForm.invalid">提交</button>
    </form>
  `
})
export class ReactiveFormComponent {
  reactiveForm = new FormGroup({
    username: new FormControl('', [
      Validators.required,
      usernameLengthValidator(3, 10)
    ], [
      usernameExistsValidator(this.http)
    ])
  });

  constructor(private http: HttpClient) { }
}

在这个示例中,我们在 FormControl 中同时使用了同步和异步自定义验证器。当验证不通过时,会显示相应的错误信息。

五、应用场景

5.1 复杂业务逻辑验证

在一些复杂的业务场景中,内置的验证规则无法满足需求。比如在一个注册表单中,要求两次输入的密码必须一致,这就需要自定义验证器来实现。

5.2 与后端数据交互验证

当需要与后端数据进行交互验证时,比如检查用户名是否已存在,就需要使用异步自定义验证器。

六、技术优缺点

6.1 优点

  • 灵活性高:可以根据具体需求创建各种复杂的验证逻辑。
  • 可复用性强:自定义验证器可以在多个表单中重复使用。
  • 与 Angular 框架集成良好:能很好地与 Angular 的表单模块结合使用。

6.2 缺点

  • 学习成本较高:对于初学者来说,理解和使用自定义验证器可能有一定难度。
  • 代码复杂度增加:随着验证逻辑的复杂,代码量会增加,维护难度也会提高。

七、注意事项

7.1 异步验证器的性能问题

异步验证器会发起异步请求,可能会影响性能。在使用时要注意合理控制请求频率,避免频繁请求。

7.2 验证器的顺序

在使用多个验证器时,要注意验证器的顺序。同步验证器会先执行,异步验证器会在同步验证器通过后执行。

八、文章总结

通过本文的介绍,我们了解了 Angular 中表单验证的基础,包括模板驱动表单和响应式表单的验证方式。同时,我们学习了如何创建同步和异步自定义验证器,并将其应用到表单中。自定义验证器能让我们构建出复杂的表单验证逻辑,满足各种业务需求。但在使用时,要注意其优缺点和一些注意事项,以确保代码的性能和可维护性。