在前端开发中,表单是用户与应用交互的核心载体,而表单验证则是保障数据合法性、提升用户体验的关键环节。Angular 作为一款成熟的前端框架,提供了一套强大且灵活的表单验证体系,既包含开箱即用的内置验证器,也支持开发者根据业务需求定制自定义验证器。本文将从基础到进阶,全面解析 Angular 表单验证的实现方式,帮助你轻松搞定各类表单验证场景。
一、Angular 表单基础:模板驱动 vs 响应式表单
在开始验证之前,先明确 Angular 的两种表单模式 —— 这是理解验证逻辑的前提:
- 模板驱动表单:验证逻辑主要写在模板中,依赖
ngModel等指令,适合简单表单场景,语法更贴近原生 HTML。 - 响应式表单:基于 ReactiveFormsModule,验证逻辑写在组件类中,通过 FormControl/FormGroup/FormArray 管理表单状态,适合复杂表单,便于测试和维护。
本文会以响应式表单为主讲解(更推荐在实际项目中使用),同时补充模板驱动表单的验证方式。
二、内置验证器(Validators):开箱即用的验证能力
Angular 在@angular/forms中提供了Validators类,内置了常用的验证规则,无需手动编写校验逻辑,直接调用即可。
1. 常用内置验证器清单
| 验证器 | 作用 | 适用类型 |
|---|---|---|
Validators.required | 必选字段 | 所有类型 |
Validators.minLength(n) | 最小长度 | 字符串 / 数组 |
Validators.maxLength(n) | 最大长度 | 字符串 / 数组 |
Validators.min(n) | 最小值 | 数字 |
Validators.max(n) | 最大值 | 数字 |
Validators.pattern(regex) | 正则匹配 | 字符串 |
Validators.email | 邮箱格式 | 字符串 |
Validators.nullValidator | 空验证(无校验) | 所有类型 |
2. 响应式表单中使用内置验证器
步骤 1:导入核心模块
在组件所属的模块中导入ReactiveFormsModule:
// app.module.ts import { NgModule } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; @NgModule({ imports: [BrowserModule, ReactiveFormsModule], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule { }步骤 2:组件类中定义表单并添加验证
// app.component.ts import { Component } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { // 定义表单组,为每个控件添加验证规则 userForm = new FormGroup({ username: new FormControl('', [ Validators.required, // 必选 Validators.minLength(3), // 最小长度3 Validators.maxLength(10) // 最大长度10 ]), email: new FormControl('', [ Validators.required, Validators.email // 邮箱格式 ]), age: new FormControl(0, [ Validators.min(18), // 最小18岁 Validators.max(120) // 最大120岁 ]), phone: new FormControl('', [ Validators.pattern(/^1[3-9]\d{9}$/) // 手机号正则 ]) }); // 提交表单 onSubmit() { if (this.userForm.invalid) { // 标记所有控件为已触碰,触发错误提示 Object.keys(this.userForm.controls).forEach(key => { this.userForm.controls[key].markAsTouched(); }); return; } console.log('表单提交成功:', this.userForm.value); } }步骤 3:模板中展示验证错误
<!-- app.component.html --> <form [formGroup]="userForm" (ngSubmit)="onSubmit()"> <!-- 用户名 --> <div> <label>用户名:</label> <input type="text" formControlName="username"> <!-- 错误提示 --> <div *ngIf="username.invalid && (username.dirty || username.touched)"> <span *ngIf="username.errors?.required">用户名不能为空</span> <span *ngIf="username.errors?.minlength">用户名至少3个字符</span> <span *ngIf="username.errors?.maxlength">用户名最多10个字符</span> </div> </div> <!-- 邮箱 --> <div> <label>邮箱:</label> <input type="email" formControlName="email"> <div *ngIf="email.invalid && (email.dirty || email.touched)"> <span *ngIf="email.errors?.required">邮箱不能为空</span> <span *ngIf="email.errors?.email">请输入有效的邮箱格式</span> </div> </div> <!-- 年龄 --> <div> <label>年龄:</label> <input type="number" formControlName="age"> <div *ngIf="age.invalid && (age.dirty || age.touched)"> <span *ngIf="age.errors?.min">年龄不能小于18岁</span> <span *ngIf="age.errors?.max">年龄不能大于120岁</span> </div> </div> <!-- 手机号 --> <div> <label>手机号:</label> <input type="text" formControlName="phone"> <div *ngIf="phone.invalid && (phone.dirty || phone.touched)"> <span *ngIf="phone.errors?.pattern">请输入有效的手机号</span> </div> </div> <button type="submit">提交</button> </form> <!-- 便捷获取控件的getter --> <pre>表单状态:{{ userForm.status | json }}</pre> <pre>表单错误:{{ userForm.errors | json }}</pre>补充:添加控件 getter 简化模板代码
在组件类中添加 getter,避免模板中重复写userForm.controls.username:
// app.component.ts get username() { return this.userForm.get('username')!; } get email() { return this.userForm.get('email')!; } get age() { return this.userForm.get('age')!; } get phone() { return this.userForm.get('phone')!; }3. 模板驱动表单中使用内置验证器
模板驱动表单通过指令(如required、minlength)直接在模板中声明验证规则:
<form #templateForm="ngForm" (ngSubmit)="onTemplateSubmit(templateForm)"> <div> <label>用户名:</label> <input type="text" name="username" ngModel required minlength="3" maxlength="10" #username="ngModel"> <div *ngIf="username.invalid && (username.dirty || username.touched)"> <span *ngIf="username.errors?.required">用户名不能为空</span> <span *ngIf="username.errors?.minlength">用户名至少3个字符</span> </div> </div> <button type="submit">提交</button> </form>组件类中处理提交:
onTemplateSubmit(form: NgForm) { if (form.invalid) return; console.log('模板表单提交:', form.value); }三、自定义验证器:满足个性化业务需求
内置验证器只能覆盖通用场景,实际项目中往往需要定制化验证(如密码强度、两次密码一致、身份证号校验等)。Angular 支持两种自定义验证器:同步验证器和异步验证器。
1. 同步自定义验证器
适用于无需异步请求的验证场景(如密码强度、两次密码一致)。
规则:
- 接收
FormControl作为参数; - 返回
{ [key: string]: any }(验证失败)或null(验证成功); - 可直接定义为函数,或封装为可传参的高阶函数。
示例 1:密码强度验证(简单版)
要求密码包含字母 + 数字,长度≥8:
// 自定义同步验证器:密码强度 export function passwordStrengthValidator(control: FormControl): { [key: string]: boolean } | null { const value = control.value; if (!value) return null; // 空值不校验(交给required) // 正则:包含字母和数字,长度≥8 const hasLetter = /[a-zA-Z]/.test(value); const hasNumber = /\d/.test(value); const isValid = hasLetter && hasNumber && value.length >= 8; return isValid ? null : { passwordStrength: true }; }示例 2:两次密码一致验证
校验密码和确认密码是否相同(需作用于 FormGroup):
// 自定义同步验证器:两次密码一致 export function confirmPasswordValidator(control: FormGroup): { [key: string]: boolean } | null { const password = control.get('password')?.value; const confirmPwd = control.get('confirmPassword')?.value; return password === confirmPwd ? null : { confirmPwd: true }; }组件中使用同步自定义验证器
// app.component.ts import { passwordStrengthValidator, confirmPasswordValidator } from './validators'; @Component({...}) export class AppComponent { // 定义带自定义验证的表单 pwdForm = new FormGroup({ password: new FormControl('', [ Validators.required, passwordStrengthValidator // 密码强度验证 ]), confirmPassword: new FormControl('', [ Validators.required ]) }, { validators: confirmPasswordValidator }); // 表单级验证 // getter get password() { return this.pwdForm.get('password')!; } get confirmPassword() { return this.pwdForm.get('confirmPassword')!; } onPwdSubmit() { if (this.pwdForm.invalid) { this.pwdForm.markAllAsTouched(); return; } console.log('密码验证通过:', this.pwdForm.value); } }模板中展示自定义验证错误
<form [formGroup]="pwdForm" (ngSubmit)="onPwdSubmit()"> <!-- 密码 --> <div> <label>密码:</label> <input type="password" formControlName="password"> <div *ngIf="password.invalid && (password.dirty || password.touched)"> <span *ngIf="password.errors?.required">密码不能为空</span> <span *ngIf="password.errors?.passwordStrength">密码需包含字母+数字,长度≥8</span> </div> </div> <!-- 确认密码 --> <div> <label>确认密码:</label> <input type="password" formControlName="confirmPassword"> <div *ngIf="confirmPassword.invalid && (confirmPassword.dirty || confirmPassword.touched)"> <span *ngIf="confirmPassword.errors?.required">确认密码不能为空</span> <span *ngIf="pwdForm.errors?.confirmPwd && !confirmPassword.errors?.required">两次密码不一致</span> </div> </div> <button type="submit">提交</button> </form>2. 异步自定义验证器
适用于需要异步请求的验证场景(如校验用户名是否已存在、手机号是否已注册)。
规则:
- 接收
FormControl作为参数; - 返回
Observable<{ [key: string]: any } | null>或Promise<{ [key: string]: any } | null>; - 需注意防抖,避免频繁请求。
示例:校验用户名是否已存在
// 模拟异步请求:检查用户名是否存在 export function checkUsernameExistsValidator(http: HttpClient) { // 返回异步验证器函数 return (control: FormControl): Observable<{ [key: string]: boolean } | null> => { const username = control.value; if (!username) return of(null); // 空值不校验 // 防抖:300ms后发送请求 return of(username).pipe( debounceTime(300), switchMap(name => { // 模拟API请求:/api/check-username?name=xxx return http.get<{ exists: boolean }>(`/api/check-username?name=${name}`).pipe( map(res => res.exists ? { usernameExists: true } : null), catchError(() => of({ usernameExists: true })) // 异常默认校验失败 ); }) ); }; }组件中使用异步自定义验证器
// app.component.ts import { checkUsernameExistsValidator } from './validators'; import { HttpClient } from '@angular/common/http'; @Component({...}) export class AppComponent { userCheckForm: FormGroup; constructor(private http: HttpClient) { this.userCheckForm = new FormGroup({ username: new FormControl('', [Validators.required], // 同步验证器 [checkUsernameExistsValidator(this.http)] // 异步验证器(第三个参数) ) }); } get username() { return this.userCheckForm.get('username')!; } }模板中处理异步验证状态
<form [formGroup]="userCheckForm"> <div> <label>用户名:</label> <input type="text" formControlName="username"> <!-- 加载状态 --> <span *ngIf="username.pending">校验中...</span> <!-- 错误提示 --> <div *ngIf="username.invalid && (username.dirty || username.touched)"> <span *ngIf="username.errors?.required">用户名不能为空</span> <span *ngIf="username.errors?.usernameExists">用户名已存在</span> </div> </div> </form>四、验证器的高级用法
1. 动态添加 / 移除验证器
通过setValidators()/clearValidators()动态修改验证规则:
// 动态添加验证器 this.username.setValidators([Validators.required, Validators.minLength(3)]); // 动态移除所有验证器 this.username.clearValidators(); // 必须调用updateValueAndValidity()使修改生效 this.username.updateValueAndValidity();2. 全局自定义验证器
将常用验证器注册为指令,在模板驱动表单中直接使用:
// password-strength.directive.ts import { Directive } from '@angular/core'; import { NG_VALIDATORS, Validator, FormControl } from '@angular/forms'; import { passwordStrengthValidator } from './validators'; @Directive({ selector: '[appPasswordStrength]', providers: [{ provide: NG_VALIDATORS, useExisting: PasswordStrengthDirective, multi: true }] }) export class PasswordStrengthDirective implements Validator { validate(control: FormControl): { [key: string]: any } | null { return passwordStrengthValidator(control); } }模板中使用:
<input type="password" ngModel appPasswordStrength name="password">五、最佳实践
- 优先使用响应式表单:验证逻辑集中在组件类,便于复用、测试和维护;
- 防抖异步验证:避免频繁发送请求,提升性能;
- 合理的错误提示:仅在用户触碰控件后展示错误,避免初始加载时的冗余提示;
- 表单状态管理:利用
dirty/touched/pending等状态精准控制错误展示; - 验证器复用:将通用自定义验证器封装为独立文件,便于跨组件使用;
- 避免过度验证:非必要的验证会增加用户负担,平衡验证强度与体验。
六、总结
Angular 的表单验证体系兼顾了易用性和灵活性:内置验证器覆盖了大部分通用场景,开箱即用;自定义验证器则能满足个性化的业务需求,无论是同步还是异步场景都能轻松应对。掌握内置验证器的使用方式,以及自定义验证器的编写思路,能够让你在开发中高效处理各类表单验证问题,提升应用的稳定性和用户体验。
希望本文能帮助你深入理解 Angular 表单验证,如果你有更多复杂的验证场景或问题,欢迎在评论区交流!