@angular/forms 源码剖析之 Validators

我们晓得,@angular/forms 包主要用来处理表单题目的,而表单题目非常主要的一个功用就是表单校验功用。数据校验非常主要,不单单议前端在发要求给后端前须要校验数据,后端对前端发来的数据也须要校验其有效性和逻辑性,尤其在存入数据库前还得校验数据的有效性。 @angular/forms 定义了一个 Validator 接口,并内置了 RequiredValidatorCheckboxRequiredValidatorEmailValidatorMinLengthValidatorMaxLengthValidatorPatternValidator 六个经常运用的校验指令,每个 validator 都完成了 Validator 接口。这些校验指令的运用很简朴,比方运用 EmailValidatorRequiredValidator 指令来校验输入的数据得是 email 且不能为空:

<input type="email" name="email" ngModel email required>

如许输入的假如不是 email 花样,EmailValidator 指令就会校验毛病,会给 host(这里也就是 input 元素)增加 ‘ng-invalid’ class,如许开发者能够给这个 class 增加一些 css 结果,进步用户体验。那末,其内部运转历程是如何的呢?

实际上,上面 demo 中不单单议绑定了 NgModel 指令,还绑定了 EmailValidatorRequiredValidator 两个 validators 指令。指令在实例化时是根据声明递次顺次举行的,有依靠的指令则置后,FormsModule 先是声清楚明了 RequiredValidator 指令,然后是 EmailValidator 指令,末了才是 NgModel,所以实例化递次是 RequiredValidator -> EmailValidator -> NgModel,同时由于 NgModel 依靠于 NG_VALIDATORS,所以就算 NgModel 声明在前也会被置后实例化。RequiredValidatorEmailValidator 在实例化历程当中都邑供应 REQUIRED_VALIDATOREMAIL_VALIDATOR 两个效劳,而且 StaticProvider 的 multi 属性设置为 true,如许能够允许有多个依靠效劳(这里是 RequiredValidator 和 EmailValidator 对象)公用一个令牌(这里是 NG_VALIDATORS),multi 属性作用能够检察源码中申明。当 NgModel 实例化时,其组织依靠于 @Self() NG_VALIDATORS@Self() 示意从 NgModel 指令挂载的宿主元素中去查找这个令牌具有的效劳,NgModel 没有供应 NG_VALIDATORS,然则挂载在 input 宿主元素上的 REQUIRED_VALIDATOREMAIL_VALIDATOR 却供应了这个效劳,所以 NgModel 的依靠 validators 就是这两个指令构成的对象数组。

NgModel 在实例化时,由于没有父控件容器,所以会挪用 _setUpStandalone(),从而挪用 setUpControl() 要领设置 FormControl 对象的 同步 validator 依靠(假如有异步 validator 依靠,也同理),这个依靠是挪用 Validators.compose() 返回的一个 ValidatorFn 函数。而 Validators.compose() 参数挪用的是 NgModel.validator,也就是挪用 composeValidators 取得 ValidatorFn,内部会挪用 normalizeValidator() 函数转换为为 (AbstractControl) => Validator.validate()。所以,和 input 控件绑定的 FormControl 对象就有了同步 validator 数据校验器。那在 input 输入框内输入数据时,校验器是在什么时候被运转的呢?

NgModel 实例化时,还安装了一个 视图数据更新回调,如许当 input 视图内的数据更新时,就会运转这个回调,该回调会更新 FormControl 的 value 值,即 FormControl.setValue() 函数,内部会挪用 updateValueAndValidity,从而最先 运转数据校验器,上文说到 FormControl 的 validator 依靠实际上是 Validators.compose() 返回的函数,所以此时会运转 这个回调函数,而这个 presentValidators 是 (AbstractControl) => RequiredValidator.validate() 和 (AbstractControl) => EmailValidator.validate() 构成的数组,然后顺次 运转 这两个 Validator 的 validate() 函数。假如校验毛病,就返回 ValidationErrors,比方 email 校验器返回的是 {’email’: true}。这里还需注重的是,Validator 指令里的 validate() 函数实际上挪用的照样 Validator 类 的对应的静态函数,如许考证器指令能够直接在模板里运用,而 Validator 类的静态函数能够在 相应式表单 中运用。校验器运转完成后,会设置 FormControl.errors 属性,从而盘算 FormControl 的 status 属性,假定校验毛病,则 status 属性值为 INVALID。那假如校验毛病,input 的 class 为什么会增加 ‘ng-invalid’ 呢?由于实际上另有一个 NgControlStatus 指令 也在绑定这个 input 元素,该指令的依靠会从当前挂载的宿主元素查找 NgControl,本 demo 中就是 NgModel 指令,NgControlStatus 指令 的 host 属性中的 ‘[class.ng-invalid]’: ‘ngClassInvalid’,会运转 ngClassInvalid() 函数推断是不是会有 ‘ng-invalid’ class,而校验毛病时,该函数运转结果是 true,由于它读取的是 FormControl.invalid 属性,则 ‘ng-invalid’ class 就会被增加到 input 元素上。同理,其他 class 如 pending、dirty 等也一样道理。如许就理解了校验器的全部运转历程,也包含为什么校验毛病时会自动增加形貌控件状况的 ‘ng-invalid’ class

我们已理解了 Validators 的内部运转流程,如许写一个自定义的 Validator 就很简朴了(固然,写一个自定义的 Validator 不须要去相识 Validator 内部运转道理)。比方,写一个自定义校验器 ForbiddenValidator,input 输入内容不能另有某些字符串,那能够模拟 @angular/forms 中的内置校验器 MinLengthValidator 写法:

import {Validators as FormValidators} from '@angular/forms';

export class Validators extends FormValidators {
  static forbidden(forbidden: string): ValidatorFn {
    return (control: AbstractControl): ValidationErrors | null => {
      return (new RegExp(forbidden)).test(control.value) ? {forbidden: true} : null;
    }
  }
}

export const FORBIDDEN_VALIDATOR: StaticProvider = {
  provide: NG_VALIDATORS,
  useExisting: forwardRef(() => ForbiddenValidator),
  multi: true
};

@Directive({
  selector:
    ':not([type=checkbox])[forbidden][formControlName],:not([type=checkbox])[forbidden][formControl],:not([type=checkbox])[forbidden][ngModel]',
  providers: [FORBIDDEN_VALIDATOR],
})
export class ForbiddenValidator implements Validator{
  private _onChange: () => void;
  private _validator: ValidatorFn;
  
  @Input() forbidden: string;
  
  ngOnChanges(changes: SimpleChanges) {
    if ('forbidden' in changes) {
      this._createValidator();
      if (this._onChange) this._onChange();
    }
  }
  
  registerOnValidatorChange(fn: () => void): void {
    this._onChange = fn;
  }
  
  validate(c: AbstractControl): ValidationErrors | null {
    return this.forbidden ? this._validator(c) : null;
  }
  
  private _createValidator(): void {
    this._validator = Validators.forbidden(this.forbidden);
  }
}

如许就能够在组件模板中运用了:

@Component(
{
    template: `
        <h2>Template-Driven Form</h2>
        <input type="email" name="email" [ngModel]="email" email required [forbidden]="forbiddenText">
        <h2>Reactive-Driven Form</h2>
        <input type="email" name="email" [formControl]="emailFormControl" email required [forbidden]="forbiddenText">
        <h2>Update Forbidden Text</h2>
        <input [(ngModel)]="forbiddenText">
    `
})
export class AppComponent {
    // custom validator
      forbiddenText = 'test';
      email = 'test@test.com';
      emailFormControl = new FormControl('test@test.com', [Validators.forbidden(this.forbiddenText)]);
}

完全代码可拜见 stackblitz demo

所以,在理解了 Validator 内部运转道理后,不单单议能够写自定义的 Validator,该 Validator 能够用于模板驱动表单也能够用于相应式表单,还能邃晓为啥须要那末写,这个很主要!

也可浏览 @angular/forms 相干文章相识 NgModel 双向绑定内部道理:@angular/forms 源码剖析之双向绑定

    原文作者:lx1036
    原文地址: https://segmentfault.com/a/1190000015862319
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞