iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 6
0
Modern Web

從巨人的 Tip 看 Angular系列 第 6

[Day 6] NG_VALIDATOR,自訂表單驗證器的二三事

  • 分享至 

  • xImage
  •  

OK!前幾天都在分享、分析 Dependency Injection 的相關 Tip 與原始碼,今天當然也是要繼續啊!

今天要來分享的 Tip,是最近剛得到 Angular GDE 稱號的 Alex Inkin 在 Twitter 上發布的內容:

This article is inspired by the tweet from Alex Inkin, an Angular GDE, posted on Twitter (link). Thank him for allowing me to use his code in this article.

@Directive({
  selector: "[validator]",
  providers: [
    {
      provide: NG_VALIDATORS,
      useExisting: ValidatorDirective,
      multi: true
    }
  ]
})
export class ValidatorDirective implements Validator, OnChanges {
  @Input()
  validator: ValidatorFn = () => null;

  private onChange: Function = () => {};

  validate(control: AbstractControl): ValidationErrors | null {
    return this.validator(control);
  }

  registerOnValidatorChange(onChange: Function) {
    this.onChange = onChange;
  }

  ngOnChanges() {
    this.onChange();
  }

  ngOnDestroy() {
    this.validator = () => null;
  }
}

↑ Block 1

透過重新提供 NG_VALIDATOR 這個內建的 DI Token,就可以免去在 FormControl 上一直 setValidators 的行為。

爬 code 啦

今天的重點是,這邊的 FormControl 是怎麼取用 Validator 的?

首先要看到的是 html 內的 FormControlName Directive,我所關心的實作部分如下(完整版):

@Directive({selector: '[formControlName]', providers: [controlNameBinding]})
export class FormControlName extends NgControl implements OnChanges, OnDestroy {
  // ... 略
  constructor(
      @Optional() @Host() @SkipSelf() parent: ControlContainer,
      @Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>,
      @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators:
          Array<AsyncValidator|AsyncValidatorFn>,
      @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[],
      @Optional() @Inject(NG_MODEL_WITH_FORM_CONTROL_WARNING) private _ngModelWarningConfig: string|
      null) {
    super();
    this._parent = parent;
    this._rawValidators = validators || [];
    this._rawAsyncValidators = asyncValidators || [];
    this.valueAccessor = selectValueAccessor(this, valueAccessors);
  }
  // ... 略
}

↑ Block 2

當使用 FormControlName 他,它的 constructor 會去尋找有沒有適合的 NG_VALIDATORS 可以用,有的話就將它的實體注入,我們後續會再提到 @Optional 與 @Self 還有其他與 Dependency Injection 相關的 decorator。

但是只看到這邊不足以解釋當透過下拉選單選取另一個 validator 時,是怎麼同時觸發驗證的。

來看一下 Angular 表單驗證器一定要實作的這個介面:Validator

export interface Validator {
  /**
   * @description
   * Method that performs synchronous validation against the provided control.
   *
   * @param control The control to validate against.
   *
   * @returns A map of validation errors if validation fails,
   * otherwise null.
   */
  validate(control: AbstractControl): ValidationErrors|null;

  /**
   * @description
   * Registers a callback function to call when the validator inputs change.
   *
   * @param fn The callback function
   */
  registerOnValidatorChange?(fn: () => void): void;
}

每一個表單驗證器都必須實作的只有 validate 這個用來做驗證的方法,registerOnValidatorChange 則是選用的方法,也是這一個 Tip 實現的關鍵。

回到 Block 1,它將傳入的 fn 箭頭函式綁定到內部的變數上,並在 ngOnChange 內呼叫,只要我們變動了傳入的 validator,就會呼叫這個 fn。

至於這個 fn 是何方神聖,可以在這邊找到:

export function setUpControl(control: FormControl, dir: NgControl): void {
  // ... 略
  // re-run validation when validator binding changes, e.g. minlength=3 -> minlength=4

  dir._rawValidators.forEach((validator: Validator|ValidatorFn) => {
    if ((<Validator>validator).registerOnValidatorChange)
      (<Validator>validator).registerOnValidatorChange!(() => control.updateValueAndValidity());
  });

  // ... 略
}

每次呼叫 fn 的時候,就會呼叫 FormControl 的 updateValueAndValidity 方法,觸發驗證!

以上就是今天所要分享的 Tip!以及它的背後原理!

Thank Alex again! Follow him on Twitter to get more Angular tips :)

倒數 24 天!

以下按照入團順序列出我們團隊夥伴的系列文章!


上一篇
[Day 5] 在建立 InjectionToken 的時候加入 Dependency
下一篇
[Day 7] FormGrop、FormControlName 與 FormControl 之間的秘密
系列文
從巨人的 Tip 看 Angular30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言