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 的行為。
今天的重點是,這邊的 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 :)
以下按照入團順序列出我們團隊夥伴的系列文章!