文章转自我的语雀:https://www.yuque.com/liuyin-zzwa0/angular/form-validation
模板驱动验证
模板驱动表单中添加验证机制,你要添加一些验证属性,就像原生的 HTML 表单验证器。 Angular 会用指令来匹配这些具有验证功能的指令。
每当表单控件中的值发生变化时,Angular 就会进行验证,并生成一个验证错误的列表(对应着 INVALID 状态)或者 null(对应着 VALID 状态)。
Name is required.
Name must be at least 4 characters long.
Name cannot be Bob.
注意以下几点:
-
元素带有一些 HTML 验证属性:
required
和 minlength。它还带有一个自定义的验证器指令forbiddenName
。要了解更多信息,参见自定义验证器一节。 - name="ngModel" 把 NgModel 导出成了一个名叫
name
的局部变量。NgModel 把自己控制的 FormControl 实例的属性映射出去,让你能在模板中检查控件的状态,比如valid
和dirty
。要了解完整的控件属性,参见 API 参考手册中的AbstractControl。 - 元素的
*[ngIf](https://angular.cn/api/common/NgIf)
展示了一组嵌套的消息div
,但是只在有“name”错误和控制器为dirty
或者touched
时才出现。- 每个嵌套的
为其中一个可能出现的验证错误显示一条自定义消息。比如required
、minlength 和forbiddenName
。以上及一下大多都是官方文档上Copy的,ε=ε=ε=┏(゜ロ゜;)┛
响应式表单的验证
在响应式表单中,权威数据源是其组件类。不应该通过模板上的属性来添加验证器,而应该在组件类中直接把验证器函数添加到表单控件模型上(FormControl)。然后,一旦控件发生了变化,Angular 就会调用这些函数。
验证器函数
有两种验证器函数:同步验证器和异步验证器。
- 同步验证器函数接受一个控件实例,然后返回一组验证错误或
null
。你可以在实例化一个 FormControl 时把它作为构造函数的第二个参数传进去。 - 异步验证器函数接受一个控件实例,并返回一个承诺(Promise)或可观察对象(Observable),它们稍后会发出一组验证错误或者
null
。你可以在实例化一个 FormControl 时把它作为构造函数的第三个参数传进去。
注意:出于性能方面的考虑,只有在所有同步验证器都通过之后,Angular 才会运行异步验证器。当每一个异步验证器都执行完之后,才会设置这些验证错误。
内置验证器
模板驱动表单中可用的那些属性型验证器
class Validators { static min(min: number): ValidatorFn static max(max: number): ValidatorFn static required(control: AbstractControl): ValidationErrors | null static requiredTrue(control: AbstractControl): ValidationErrors | null static email(control: AbstractControl): ValidationErrors | null static minLength(minLength: number): ValidatorFn static maxLength(maxLength: number): ValidatorFn static pattern(pattern: string | RegExp): ValidatorFn static nullValidator(control: AbstractControl): ValidationErrors | null static compose(validators: ValidatorFn[]): ValidatorFn | null static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn | null }
用法如下:
this.validateForm = this.fb.group({ userName: new FormControl(null, { validators: [Validators.required, Validators.pattern(/^[^\s]|[^\s]$/g)], updateOn: 'change' }), password: [null, [Validators.required, Validators.pattern(/^\S+$/)]], verifCode: [null, Validators.required] });
自定义验证器
由于内置验证器无法适用于所有应用场景,有时候还是得创建自定义验证器。
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn { return (control: AbstractControl): {[key: string]: any} | null => { const forbidden = nameRe.test(control.value); return forbidden ? {'forbiddenName': {value: control.value}} : null; }; }
forbiddenNameValidator
工厂函数返回配置好的验证器函数。 该函数接受一个 Angular 控制器对象,并在控制器值有效时返回 null,或无效时返回验证错误对象。添加响应式表单
在响应式表单组件中,添加自定义验证器相当简单。你所要做的一切就是直接把这个函数传给 FormControl 。
this.heroForm = new FormGroup({ 'name': new FormControl('', [ Validators.required, Validators.minLength(4), forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator. ]), 'alterEgo': [null], 'power': [null], });
添加到模板驱动表单
在模板驱动表单中,你不用直接访问 FormControl 实例。所以不能像响应式表单中那样把验证器传进去,而应该在模板中添加一个指令。
ForbiddenValidatorDirective
指令相当于forbiddenNameValidator
的包装器。
Angular 在验证过程中能识别出指令的作用,是因为指令把自己注册成了 NG_VALIDATORS 提供商,该提供商拥有一组可扩展的验证器。@Directive({ selector: '[appForbiddenName]', providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}] }) export class ForbiddenValidatorDirective implements Validator { @Input('appForbiddenName') forbiddenName: string; validate(control: AbstractControl): {[key: string]: any} | null { return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control) : null; } } // use providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]
使用
跨字段交叉验证
添加到响应式表单
实现要点
AbstractControlOptions
接口类型:export declare interface AbstractControlOptions { /** * @description * The list of validators applied to a control. */ validators?: ValidatorFn | ValidatorFn[] | null; /** * @description * The list of async validators applied to control. */ asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[] | null; /** * @description * The event name for control to update upon. */ updateOn?: 'change' | 'blur' | 'submit'; }
this.heroForm = new FormGroup({ 'name': new FormControl(null, [ Validators.required, Validators.minLength(4), forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator. ]), 'alterEgo': [null] 'power': [null, Validators.required] }, { updateOn: 'change', validators: (form: FormGroup) => { const { controls: { name, alterEgo }, dirty } = form; if (dirty) { return alterEgo.value ? { 'identityRevealed': true } : null; } return null; } });
添加到模板驱动表单中
创建一个指令,它会包装这个验证器函数。我们使用 NG_VALIDATORS 令牌来把它作为验证器提供出来。
@Directive({ selector: '[appIdentityRevealed]', providers: [{ provide: NG_VALIDATORS, useExisting: IdentityRevealedValidatorDirective, multi: true }] }) export class IdentityRevealedValidatorDirective implements Validator { validate(control: AbstractControl): ValidationErrors { return identityRevealedValidator(control) } }
将指令添加到 HTML 模板中。由于验证器必须注册在表单的最高层,所以我们要把该指令放在
form
标签上。显示校验信息
异步验证
响应式表单实现异步验证器
还是依靠
AbstractControlOptions
接口类型实现this.heroForm = new FormGroup({ 'name': new FormControl(null, [ Validators.required, Validators.minLength(4), forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator. ]), 'alterEgo': [null] 'power': [null, { updateOn: 'bulr', validators: Validators.required, asyncValidators: (form: FormGroup) => { const { controls: { name, alterEgo }, dirty } = form; return new Promise(resolve =>{ if (dirty) { resolve(alterEgo.value ? { 'identityRevealed': true } : null); } resolve(null); }); } }] });
模板驱动表单实现自定义异步验证器
基础概念
就像同步验证器有 ValidatorFn 和 Validator` 接口一样,异步验证器也有自己的对应物:AsyncValidatorFn 和 AsyncValidator。
它们非常像,但是有下列不同:- 它们必须返回承诺(Promise)或可观察对象(Observable),
- 返回的可观察对象必须是有限的,也就是说,它必须在某个时间点结束(complete)。要把无尽的可观察对象转换成有限的,可以使用
first
、last
、take
或takeUntil
等过滤型管道对其进行处理。
注意!异步验证总是会在同步验证之后执行,并且只有当同步验证成功了之后才会执行。如果更基本的验证方法已经失败了,那么这能让表单避免进行可能会很昂贵的异步验证过程,比如 HTTP 请求。
在异步验证器开始之后,表单控件会进入pending
状态。你可以监视该控件的pending
属性,利用它来给用户一些视觉反馈,表明正在进行验证。创建验证器类
@Injectable({ providedIn: 'root' }) export class UniqueAlterEgoValidator implements AsyncValidator { constructor(private heroesService: HeroesService) {} validate( ctrl: AbstractControl ): Promise
| Observable { return this.heroesService.isAlterEgoTaken(ctrl.value).pipe( map(isTaken => (isTaken ? { uniqueAlterEgo: true } : null)), catchError(() => null) ); } } 在template中使用:
异步验证性能上的注意事项
默认情况下,每当表单值变化之后,都会执行所有验证器。对于同步验证器,没有什么会显著影响应用性能的地方。不过,异步验证器通常会执行某种 HTTP 请求来对控件进行验证。如果在每次按键之后都发出 HTTP 请求会给后端 API 带来沉重的负担,应该尽量避免。
我们可以把updateOn
属性从change
(默认值)改成submit
或blur
来推迟表单验证的更新时机。对于模板驱动表单:
对于响应式表单:
new FormControl('', {updateOn: 'blur'});
- 每个嵌套的