一个没有验证的表单,就像一座不设防的城池,任何人、任何「数据」都可以长驱直入。其结果,轻则让你的应用状态陷入混乱,重则导致脏数据入库,引发更深层次的灾难。
表单验证,就是你应用数据的「护城河」与「城墙」。它是你作为一名严谨的工程师,为保障数据质量、提升用户体验而必须掌握的「秘密武器」。幸运的是,Angular 为我们提供了 arsenal(一整套)强大而灵活的验证「兵器谱」。
两大阵营:不同的「练兵」方式#
在 Angular 中,应用验证规则的方式,根据你选择的表单类型而有所不同:
-
响应式表单:验证器是函数。你在组件的 TS 文件中,将这些函数直接传递给
FormControl的构造器。逻辑与模型在代码中紧密结合。 -
模板驱动表单:验证器是指令。你将
required,minLength等指令直接应用在模板的 HTML 元素上。逻辑体现在视图中。
虽然应用方式不同,但验证器本身的核心(无论是内置的还是自定义的)是可以通用的。
「兵器谱」之:内置验证器#
@angular/forms 中的 Validators 类,为我们提供了最常用的一批「制式兵器」,开箱即用。
-
Validators.required:必填项。 -
Validators.minLength(number):最小长度。 -
Validators.maxLength(number):最大长度。 -
Validators.pattern(regex):必须匹配正则表达式。 -
Validators.email:必须是合法的邮件格式。 -
Validators.min(number):最小值(用于数字)。 -
Validators.max(number):最大值(用于数字)。
如何使用?
响应式表单中:
import { Validators } from '@angular/forms';
this.form = this.fb.group({
// 将校验器函数数组作为第二个参数传入
username: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]]
});模板驱动表单中:
<input type="text" name="username" ngModel required minlength="3">
<input type="email" name="email" ngModel required email>「锻造神兵」之一:自定义同步验证器#
当内置兵器不称手时,我们就需要自己「锻造」一柄。比如,我们需要一个不允许输入特殊字符的验证器。
规则:一个同步验证器,就是一个接收 AbstractControl 作为参数,并在验证失败时返回一个错误对象 ValidationErrors、验证成功时返回 null 的函数。
// no-special-chars.validator.ts
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
export function noSpecialCharsValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const hasSpecialChars = /[!@#$%^&*(),.?":{}|<>]/.test(control.value);
// 如果包含特殊字符,返回一个错误对象,键名代表错误类型
return hasSpecialChars ? { hasSpecialChars: true } : null;
};
}在响应式表单中使用:
this.form = this.fb.group({
username: ['', [Validators.required, noSpecialCharsValidator()]]
});(在模板驱动表单中使用自定义验证器,需要将其包装成一个指令,相对繁琐,故在此不赘述,推荐在需要自定义验证时优先使用响应式表单。)
「锻造神兵」之二:自定义异步验证器#
当验证逻辑需要依赖外部系统时(如检查用户名是否已被注册),异步验证器就登场了。
规则:一个异步验证器,是一个接收 AbstractControl,并返回一个 Promise<ValidationErrors | null> 或 Observable<ValidationErrors | null> 的函数。
实战:检查用户名是否唯一
// unique-username.validator.ts
import { inject, Injectable } from '@angular/core';
import { AbstractControl, AsyncValidatorFn, ValidationErrors } from '@angular/forms';
import { Observable, of } from 'rxjs';
import { map, catchError, debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
import { AuthService } from './auth.service';
// 我们通常将异步验证器封装在一个服务中,以便注入其他依赖
@Injectable({ providedIn: 'root' })
export class UniqueUsernameValidator {
private authService = inject(AuthService);
createValidator(): AsyncValidatorFn {
return (control: AbstractControl): Observable<ValidationErrors | null> => {
return of(control.value).pipe(
debounceTime(500), // 防抖,避免频繁请求
distinctUntilChanged(), // 值未变则不请求
switchMap(username =>
this.authService.isUsernameTaken(username).pipe(
map(isTaken => (isTaken ? { usernameTaken: true } : null)),
catchError(() => of(null)) // 如果 API 请求失败,也视作验证通过
)
)
);
};
}
}在响应式表单中使用:
异步验证器是 FormControl 的第三个参数。
// component.ts
constructor(private uniqueValidator: UniqueUsernameValidator) {
this.form = this.fb.group({
username: [
'', // 默认值
[Validators.required], // 同步验证器
[this.uniqueValidator.createValidator()] // 异步验证器
]
});
}当异步验证正在「请求」时,FormControl 的状态会变为 PENDING,你可以利用这个状态来显示「正在验证…」之类的提示。
展示「战报」:在模板中显示错误信息#
知道了如何验证,我们还需要优雅地把「战报」(错误信息)展示给用户。
<form [formGroup]="form">
<input formControlName="username">
@if (form.get('username'); as usernameControl) {
@if (usernameControl.invalid && (usernameControl.dirty || usernameControl.touched)) {
<div class="error-feedback">
@if (usernameControl.hasError('required')) {
<span>用户名不能为空。</span>
}
@if (usernameControl.hasError('minlength')) {
<span>用户名至少需要3个字符。</span>
}
@if (usernameControl.hasError('usernameTaken')) {
<span>该用户名已被占用。</span>
}
</div>
} @else if (usernameControl.pending) {
<div class="pending-feedback">正在检查用户名可用性...</div>
}
}
</form>我们通过检查控件的 invalid, dirty, touched, pending 等状态,以及 hasError('errorKey') 方法,来精准地控制错误信息的显示时机和内容。
结语#
表单验证,是应用程序的「免疫系统」。它能将无效的、有害的「数据病菌」阻挡在外,保障内部系统的健康与纯净。Angular 提供了从「常规武器」到「可定制神兵」的全套工具,让你能从容应对各种复杂的校验场景。
掌握好这套「秘密武器」,你就能为你的应用,构建起一道真正「滴水不漏」的坚固防线。
正如《易经》有云:「君子以思患而豫防之。」意指君子深思可能发生的祸患,从而提前预防。表单验证正是这种「思患豫防」的智慧在编程世界中的体现,它是我们保障数据质量、维护系统稳定的基石。