Angular 自定义验证器(Custom Validators)完整指南:从函数签名到跨字段校验实战
文档教程知识库【免费下载链接】developer-roadmapInteractive roadmaps, guides and other educational content to help developers grow in their careers.项目地址https://gitcode.com/GitHub_Trending/de/developer-roadmap点击查看免费下载自定义验证器Custom Validator是 Angular 表单体系中用于承载专属业务校验逻辑的核心函数它解决的是内置验证器如required、minLength等无法覆盖的个性化校验场景。本篇指南以 Angular 学习路线中的表单主题为依托系统讲解自定义验证器的函数契约、错误对象结构、带参验证器工厂、响应式/模板驱动两种表单接入方式、跨字段校验、异步验证以及单元测试方法帮助你掌握在真实业务中编写、复用与测试自定义校验逻辑的完整能力。什么是自定义验证器在 Angular 中自定义验证器本质上是一个普通的 JavaScript/TypeScript 函数它接收一个表单控件的值并返回校验结果。当内置验证器无法满足业务需求时例如用户名不得包含敏感词密码与确认密码必须一致年龄必须在 18 到 65 之间你就可以通过自定义验证器来实现这些专属规则。自定义验证器遵循一个明确、统一的函数契约校验通过时返回null校验失败时返回一个验证错误对象该对象通常是一个键值对键是错误名称error name值是布尔值true或与该错误相关的细节信息。例如一个值必须包含数字的验证器失败时返回{ hasNumber: true }而一个邮箱格式不合法的验证器可以返回{ invalidEmail: { value: abc } }把出错的原始值一并带出来方便模板展示和调试。// 自定义验证器函数签名接收 AbstractControl返回 ValidationErrors | null import { AbstractControl, ValidationErrors } from angular/forms; export function mustContainNumber(control: AbstractControl): ValidationErrors | null { const value: string control.value; if (value /\d/.test(value)) { return null; // 校验通过 } return { mustContainNumber: true }; // 校验失败返回错误对象 }在 Angular 学习路线中表单Forms是整个框架的核心能力之一而自定义验证器正是让表单校验从通用走向业务化的关键环节通常与响应式表单Reactive Forms配合使用以获得最灵活的编程式控制。同步验证器函数签名与错误对象Angular 官方将同步验证器的类型定义为ValidatorFn其完整签名如下type ValidatorFn (control: AbstractControl) ValidationErrors | null;其中ValidationErrors本质上是一个以错误名称为键的对象type ValidationErrors { [key: string]: any; };关键约定约定说明返回值null表示校验通过控件进入VALID状态返回错误对象表示校验失败控件进入INVALID状态错误对象会挂到控件的errors属性上错误对象的结构键为错误名称模板中通过该名称读取值为true或携带额外信息如出错的值、期望值等空值处理若控件值为null或空字符串建议直接返回null把必填的职责留给required内置验证器避免职责重叠import { AbstractControl, ValidationErrors } from angular/forms; // 推荐写法先处理空值再校验具体业务规则 export function forbiddenName(forbidden: string): ValidatorFn { return (control: AbstractControl): ValidationErrors | null { const value: string control.value; if (!value) { return null; } if (value.includes(forbidden)) { return { forbiddenName: { value, forbidden } }; // 附带细节信息 } return null; }; }校验失败时控件上会同时挂载多个错误如果配置了多个验证器你可以通过control.errors读取并在模板中按错误名称精确展示提示信息div *ngIfusername.errors?.forbiddenName 用户名不能包含非法词 /div带参数验证器工厂函数模式很多业务校验规则需要参数如长度不能超过 10 个字符不能等于某个保留值此时不能直接写一个固定函数而要用高阶函数工厂函数外层函数接收配置参数返回一个捕获了参数的自定义验证器函数。import { AbstractControl, ValidationErrors, ValidatorFn } from angular/forms; // 工厂函数接收限制长度返回验证器 export function maxCustomLength(max: number): ValidatorFn { return (control: AbstractControl): ValidationErrors | null { const value: string control.value; if (value value.length max) { return { maxCustomLength: { requiredLength: max, actualLength: value.length } }; } return null; }; }使用时传入参数即可同一个验证器可以被不同参数复用在多个控件上this.form this.fb.group({ nickname: [, [Validators.required, maxCustomLength(10)]], slogan: [, [maxCustomLength(30)]], });在响应式表单中使用自定义验证器响应式表单基于FormControl、FormGroup、FormArray这类显式、不可变的数据结构来管理表单状态是接入自定义验证器最直接的方式——验证器以函数形式作为配置传入即可。方式一FormControl构造器import { FormControl, Validators } from angular/forms; const username new FormControl(, [ Validators.required, Validators.minLength(3), mustContainNumber, // 自定义验证器函数直接引用 forbiddenName(admin), // 带参验证器工厂调用 ]);方式二FormBuilder的group/controlimport { FormBuilder, Validators } from angular/forms; constructor(private fb: FormBuilder) {} this.profileForm this.fb.group({ username: [, [Validators.required, forbiddenName(admin)]], age: [18, [Validators.min(18), Validators.max(65)]], });多个验证器的组合执行当验证器数组中有多个验证器时Angular 会依次执行它们一旦某个验证器返回了错误对象后续验证器默认不再执行且所有已发现的错误会合并到控件的errors对象中。若需要手动合并多个验证器结果可以使用Validators.compose([...])const composed Validators.compose([ Validators.required, mustContainNumber, forbiddenName(admin), ]);触发时机updateOn默认情况下验证器在控件的valueChanges每次触发时执行。你可以通过updateOn控制触发时机减少高频校验的副作用const username new FormControl(, { validators: [forbiddenName(admin)], updateOn: blur, // 失焦时才校验blur | submit | change(默认) });在模板驱动表单中使用自定义验证器模板驱动表单在模板中通过ngModel指令自动创建并绑定控件。要让自定义验证器在模板驱动表单中生效需要把它包装成一个指令并实现 Angular 的Validator接口、注册到NG_VALIDATORS多提供者multi-provider令牌中。import { Directive } from angular/core; import { NG_VALIDATORS, Validator, AbstractControl, ValidationErrors } from angular/forms; Directive({ selector: [appForbiddenName], // 在模板中以属性指令形式使用 providers: [{ provide: NG_VALIDATORS, useExisting: ForbiddenNameDirective, multi: true, // 多提供者追加而非覆盖内置验证器 }], }) export class ForbiddenNameDirective implements Validator { validate(control: AbstractControl): ValidationErrors | null { return forbiddenName(admin)(control); // 复用同一个自定义验证器函数 } }模板中直接以属性形式挂载input typetext nameusername ngModel appForbiddenName #usernamengModel / div *ngIfusername.errors?.forbiddenName 用户名不能使用保留词 /div如果需要支持输入参数如不同的保留词可以进一步给指令添加Input()在validate()中读取Directive({ selector: [appForbiddenName], /* providers 同上 */ }) export class ForbiddenNameDirective implements Validator { Input(appForbiddenName) forbidden: string ; validate(control: AbstractControl): ValidationErrors | null { return this.forbidden ? forbiddenName(this.forbidden)(control) : null; } }input typetext nameusername ngModel [appForbiddenName]root /核心要点Validator接口只要求实现一个validate(control): ValidationErrors | null方法其返回约定与自定义验证器函数完全一致而NG_VALIDATORS是 Angular 表单模块查找模板验证器的依赖注入令牌multi: true确保你的指令验证器与内置验证器共存而非覆盖。跨字段验证基于 FormGroup 的验证器自定义验证器不仅可以作用于单个FormControl也可以作用于整个FormGroup用于实现密码与确认密码一致起始日期早于结束日期这类跨字段校验。此时验证器接收的参数是整个组FormGroup通过control.get(fieldName)读取子控件值。import { AbstractControl, ValidationErrors, ValidatorFn } from angular/forms; // 作用于 FormGroup 的自定义验证器 export function passwordsMatch(group: AbstractControl): ValidationErrors | null { const password group.get(password)?.value; const confirm group.get(confirmPassword)?.value; return password confirm ? null : { passwordsMatch: true }; }在FormBuilder.group的第二参数字段中**以单个函数而非数组**传入组级验证器this.registerForm this.fb.group( { password: [, [Validators.required, Validators.minLength(8)]], confirmPassword: [, [Validators.required]], }, { validators: passwordsMatch } // 组级验证器作用于整个 FormGroup );模板中从FormGroup的错误中读取div *ngIfregisterForm.errors?.passwordsMatch 两次输入的密码不一致 /div需要注意组级验证器产生的错误挂在FormGroup的errors上而不是某个子控件上因此模板中应从组对象读取否则会提示找不到该错误。异步验证器当校验需要请求服务端如用户名是否已被注册时应使用异步验证器。异步验证器的类型为AsyncValidatorFn返回PromiseValidationErrors | null或ObservableValidationErrors | null需可完成通常配合first()、debounceTime()使用。import { AbstractControl, ValidationErrors } from angular/forms; import { Observable, of, delay, map } from rxjs; // 模拟服务端校验 export function usernameTaken(control: AbstractControl): ObservableValidationErrors | null { const value control.value; return of([admin, root].includes(value)).pipe( delay(300), map(taken (taken ? { usernameTaken: true } : null)), ); }在响应式表单中异步验证器放在第三参数位置第二参数是同步验证器数组第三参数是异步验证器数组const username new FormControl(, { validators: [Validators.required], asyncValidators: [usernameTaken], updateOn: blur, // 异步校验建议配合 blur/submit 触发避免每次输入都发请求 });异步校验期间控件处于PENDING状态可通过control.pending在模板中展示加载提示。与严格类型化表单Typed Forms的结合从 Angular 14 开始响应式表单默认严格类型化Typed FormsFormControlstring、FormGroup{ username: FormControlstring }等类型由默认值自动推断。自定义验证器同样受益于类型系统——如果你使用FormGroup类型化泛型control.get(username)返回的是类型化的控件引用跨字段验证器读取值时可以获得编译期检查interface RegisterModel { username: string; password: string; confirmPassword: string; } this.registerForm new FormGroupRegisterModel({ username: new FormControl(, { nonNullable: true }), password: new FormControl(, { nonNullable: true }), confirmPassword: new FormControl(, { nonNullable: true }), });类型化表单让control.get(username)!.value的类型从any变为string配合自定义验证器书写时能大幅减少拼写错误和类型断言。为自定义验证器编写单元测试自定义验证器是纯函数非常适合单元测试。在 Angular 的Jasmine Karma 测试体系下只需构造FormControl并断言返回值即可无需渲染组件import { FormControl } from angular/forms; import { mustContainNumber, forbiddenName } from ./validators; describe(mustContainNumber, () { it(should return null when value contains a number, () { const control new FormControl(user123); expect(mustContainNumber(control)).toBeNull(); }); it(should return error object when value has no number, () { const control new FormControl(userabc); expect(mustContainNumber(control)).toEqual({ mustContainNumber: true }); }); }); describe(forbiddenName, () { it(should mark control invalid for forbidden word, () { const control new FormControl(hello-admin); expect(forbiddenName(admin)(control)).toEqual( jasmine.objectContaining({ forbiddenName: jasmine.anything() }) ); }); });对于模板驱动表单中包装为指令的验证器可在测试中创建宿主组件并使用TestBed注入ForbiddenNameDirective后直接调用其validate()方法断言异步验证器则通过fakeAsynctick()或async处理时序。小结与仓库资源导航自定义验证器的核心心智模型可以浓缩为一条规则返回null即通过返回错误对象即失败。在这个统一契约下你可以自由组合出单个字段校验、带参校验、跨字段校验、异步服务端校验等各类业务场景并以函数响应式或指令模板驱动两种形态接入 Angular 表单体系。在 Angular 学习路线仓库中围绕表单与校验还有一系列可以直接阅读的配套主题文档表单Forms概览表单的收集、校验与数据同步机制总览响应式表单Reactive Forms模型驱动方式下接入自定义验证器的首选场景模板驱动表单Template-driven Forms通过ngModel指令在模板中管理校验严格类型化表单Typed Forms类型化FormControl/FormGroup对验证器书写的编译期保障ControlValueAccessor自定义表单控件与 Angular 表单 API 之间的桥梁动态表单Dynamic Forms运行时动态生成控件与校验规则的高级用法测试Testing基于 Jasmine 与 Karma 验证表单与验证器行为赞分享文档教程知识库【免费下载链接】developer-roadmapInteractive roadmaps, guides and other educational content to help developers grow in their careers.项目地址https://gitcode.com/GitHub_Trending/de/developer-roadmap点击查看免费下载相关推荐beego core/validation 数据校验实战指南从字段校验到自定义验证器beego core/validation 数据校验实战指南从字段校验到自定义验证器 本文围绕 beego github.com/beego/beego/v后端Web框架CLI11 Validators 权威指南从内置校验器到自定义 transform/check 校验器CLI11 Validators 权威指南从内置校验器到自定义 transform/check 校验器 CLI11 是当前仓库 smallthinker/po人工智能大模型推理引擎本地部署ramsey/uuid 自定义验证器Custom Validator完全指南从宽松校验到严格 RFC 校验ramsey/uuid 自定义验证器Custom Validator完全指南从宽松校验到严格 RFC 校验 ramsey/uuid 默认对 UUID 字符后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考