摘要:校驗(yàn)器運(yùn)行完成后,會設(shè)置屬性,從而計(jì)算的屬性,假設(shè)校驗(yàn)錯誤,則屬性值為。這樣就理解了校驗(yàn)器的整個(gè)運(yùn)行過程,也包括為何校驗(yàn)錯誤時(shí)會自動添加描述控件狀態(tài)的。
我們知道,@angular/forms 包主要用來解決表單問題的,而表單問題非常重要的一個(gè)功能就是表單校驗(yàn)功能。數(shù)據(jù)校驗(yàn)非常重要,不僅僅前端在發(fā)請求給后端前需要校驗(yàn)數(shù)據(jù),后端對前端發(fā)來的數(shù)據(jù)也需要校驗(yàn)其有效性和邏輯性,尤其在存入數(shù)據(jù)庫前還得校驗(yàn)數(shù)據(jù)的有效性。 @angular/forms 定義了一個(gè) Validator 接口,并內(nèi)置了 RequiredValidator、CheckboxRequiredValidator、EmailValidator、MinLengthValidator、MaxLengthValidator、PatternValidator 六個(gè)常用的校驗(yàn)指令,每一個(gè) validator 都實(shí)現(xiàn)了 Validator 接口。這些校驗(yàn)指令的使用很簡單,比如使用 EmailValidator 和 RequiredValidator 指令來校驗(yàn)輸入的數(shù)據(jù)得是 email 且不能為空:
這樣輸入的如果不是 email 格式,EmailValidator 指令就會校驗(yàn)錯誤,會給 host(這里也就是 input 元素)添加 "ng-invalid" class,這樣開發(fā)者可以給這個(gè) class 添加一些 css 效果,提高用戶體驗(yàn)。那么,其內(nèi)部運(yùn)行過程是怎樣的呢?
實(shí)際上,上面 demo 中不僅僅綁定了 NgModel 指令,還綁定了 EmailValidator 和 RequiredValidator 兩個(gè) validators 指令。指令在實(shí)例化時(shí)是按照聲明順序依次進(jìn)行的,有依賴的指令則置后,FormsModule 先是聲明了 RequiredValidator 指令,然后是 EmailValidator 指令,最后才是 NgModel,所以實(shí)例化順序是 RequiredValidator -> EmailValidator -> NgModel,同時(shí)由于 NgModel 依賴于 NG_VALIDATORS,所以就算 NgModel 聲明在前也會被置后實(shí)例化。RequiredValidator 和 EmailValidator 在實(shí)例化過程中都會提供 REQUIRED_VALIDATOR 和 EMAIL_VALIDATOR 兩個(gè)服務(wù),并且 StaticProvider 的 multi 屬性設(shè)置為 true,這樣可以容許有多個(gè)依賴服務(wù)(這里是 RequiredValidator 和 EmailValidator 對象)公用一個(gè)令牌(這里是 NG_VALIDATORS),multi 屬性作用可以查看源碼中說明。當(dāng) NgModel 實(shí)例化時(shí),其構(gòu)造依賴于 @Self() NG_VALIDATORS,@Self() 表示從 NgModel 指令掛載的宿主元素中去查找這個(gè)令牌擁有的服務(wù),NgModel 沒有提供 NG_VALIDATORS,但是掛載在 input 宿主元素上的 REQUIRED_VALIDATOR 和 EMAIL_VALIDATOR 卻提供了這個(gè)服務(wù),所以 NgModel 的依賴 validators 就是這兩個(gè)指令組成的對象數(shù)組。
NgModel 在實(shí)例化時(shí),由于沒有父控件容器,所以會調(diào)用 _setUpStandalone(),從而調(diào)用 setUpControl() 方法設(shè)置 FormControl 對象的 同步 validator 依賴(如果有異步 validator 依賴,也同理),這個(gè)依賴是調(diào)用 Validators.compose() 返回的一個(gè) ValidatorFn 函數(shù)。而 Validators.compose() 參數(shù)調(diào)用的是 NgModel.validator,也就是調(diào)用 composeValidators 獲得 ValidatorFn,內(nèi)部會調(diào)用 normalizeValidator() 函數(shù)轉(zhuǎn)換為為 (AbstractControl) => Validator.validate()。所以,和 input 控件綁定的 FormControl 對象就有了同步 validator 數(shù)據(jù)校驗(yàn)器。那在 input 輸入框內(nèi)輸入數(shù)據(jù)時(shí),校驗(yàn)器是在何時(shí)被運(yùn)行的呢?
NgModel 實(shí)例化時(shí),還安裝了一個(gè) 視圖數(shù)據(jù)更新回調(diào),這樣當(dāng) input 視圖內(nèi)的數(shù)據(jù)更新時(shí),就會運(yùn)行這個(gè)回調(diào),該回調(diào)會更新 FormControl 的 value 值,即 FormControl.setValue() 函數(shù),內(nèi)部會調(diào)用 updateValueAndValidity,從而開始 運(yùn)行數(shù)據(jù)校驗(yàn)器,上文說到 FormControl 的 validator 依賴實(shí)際上是 Validators.compose() 返回的函數(shù),所以此時(shí)會運(yùn)行 這個(gè)回調(diào)函數(shù),而這個(gè) presentValidators 是 (AbstractControl) => RequiredValidator.validate() 和 (AbstractControl) => EmailValidator.validate() 組成的數(shù)組,然后依次 運(yùn)行 這兩個(gè) Validator 的 validate() 函數(shù)。如果校驗(yàn)錯誤,就返回 ValidationErrors,比如 email 校驗(yàn)器返回的是 {"email": true}。這里還需注意的是,Validator 指令里的 validate() 函數(shù)實(shí)際上調(diào)用的還是 Validator 類 的對應(yīng)的靜態(tài)函數(shù),這樣驗(yàn)證器指令可以直接在模板里使用,而 Validator 類的靜態(tài)函數(shù)可以在 響應(yīng)式表單 中使用。校驗(yàn)器運(yùn)行完成后,會設(shè)置 FormControl.errors 屬性,從而計(jì)算 FormControl 的 status 屬性,假設(shè)校驗(yàn)錯誤,則 status 屬性值為 INVALID。那如果校驗(yàn)錯誤,input 的 class 為何會添加 "ng-invalid" 呢?因?yàn)閷?shí)際上還有一個(gè) NgControlStatus 指令 也在綁定這個(gè) input 元素,該指令的依賴會從當(dāng)前掛載的宿主元素查找 NgControl,本 demo 中就是 NgModel 指令,NgControlStatus 指令 的 host 屬性中的 "[class.ng-invalid]": "ngClassInvalid",會運(yùn)行 ngClassInvalid() 函數(shù)判斷是否會有 "ng-invalid" class,而校驗(yàn)錯誤時(shí),該函數(shù)運(yùn)行結(jié)果是 true,因?yàn)樗x取的是 FormControl.invalid 屬性,則 "ng-invalid" class 就會被添加到 input 元素上。同理,其他 class 如 pending、dirty 等也同樣道理。這樣就理解了校驗(yàn)器的整個(gè)運(yùn)行過程,也包括為何校驗(yàn)錯誤時(shí)會自動添加描述控件狀態(tài)的 "ng-invalid" class。
我們已經(jīng)理解了 Validators 的內(nèi)部運(yùn)行流程,這樣寫一個(gè)自定義的 Validator 就很簡單了(當(dāng)然,寫一個(gè)自定義的 Validator 不需要去了解 Validator 內(nèi)部運(yùn)行原理)。比如,寫一個(gè)自定義校驗(yàn)器 ForbiddenValidator,input 輸入內(nèi)容不能還有某些字符串,那可以模仿 @angular/forms 中的內(nèi)置校驗(yàn)器 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: `Template-Driven Form
Reactive-Driven Form
Update Forbidden Text
` }) export class AppComponent { // custom validator forbiddenText = "test"; email = "[email protected]"; emailFormControl = new FormControl("[email protected]", [Validators.forbidden(this.forbiddenText)]); }
完整代碼可參見 stackblitz demo。
所以,在理解了 Validator 內(nèi)部運(yùn)行原理后,不僅僅可以寫自定義的 Validator,該 Validator 可以用于模板驅(qū)動表單也可以用于響應(yīng)式表單,還能明白為啥需要那么寫,這個(gè)很重要!
也可閱讀 @angular/forms 相關(guān)文章了解 NgModel 雙向綁定內(nèi)部原理:@angular/forms 源碼解析之雙向綁定。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/96539.html
摘要:由于的屬性提供了令牌,并且該令牌指向的對象就是,所以構(gòu)造函數(shù)中注入的令牌包含的對象數(shù)組只有一個(gè)。這樣的構(gòu)造函數(shù)里就會包含一個(gè)對象,然后把這個(gè)傳給對象,最后注冊回調(diào),這樣以后值更新時(shí)就會運(yùn)行。整個(gè)包的設(shè)計(jì)也是按照這種數(shù)據(jù)流形式,并不復(fù)雜。 我們知道,Angular 的 @angular/forms 包提供了 NgModel 指令,來實(shí)現(xiàn)雙向綁定,即把一個(gè) JS 變量(假設(shè)為 name)與...
摘要:在模塊里面引入要使用響應(yīng)式表單,就要從包中導(dǎo)入,并把它添加到你的的數(shù)組中。導(dǎo)入驗(yàn)證器函數(shù)響應(yīng)式表單包含了一組開箱即用的常用驗(yàn)證器函數(shù)。這些驗(yàn)證器屬性可以和響應(yīng)式表單提供的內(nèi)置驗(yàn)證器組合使用。 1:在AppModule模塊里面引入 ReactiveFormsModule 要使用響應(yīng)式表單,就要從@angular/forms包中導(dǎo)入ReactiveFormsModule,并把它添加到你的N...
摘要:在模塊里面引入要使用響應(yīng)式表單,就要從包中導(dǎo)入,并把它添加到你的的數(shù)組中。導(dǎo)入驗(yàn)證器函數(shù)響應(yīng)式表單包含了一組開箱即用的常用驗(yàn)證器函數(shù)。這些驗(yàn)證器屬性可以和響應(yīng)式表單提供的內(nèi)置驗(yàn)證器組合使用。 1:在AppModule模塊里面引入 ReactiveFormsModule 要使用響應(yīng)式表單,就要從@angular/forms包中導(dǎo)入ReactiveFormsModule,并把它添加到你的N...
摘要:在模塊里面引入要使用響應(yīng)式表單,就要從包中導(dǎo)入,并把它添加到你的的數(shù)組中。導(dǎo)入驗(yàn)證器函數(shù)響應(yīng)式表單包含了一組開箱即用的常用驗(yàn)證器函數(shù)。這些驗(yàn)證器屬性可以和響應(yīng)式表單提供的內(nèi)置驗(yàn)證器組合使用。 1:在AppModule模塊里面引入 ReactiveFormsModule 要使用響應(yīng)式表單,就要從@angular/forms包中導(dǎo)入ReactiveFormsModule,并把它添加到你的N...
閱讀 2457·2021-10-09 09:59
閱讀 2228·2021-09-23 11:30
閱讀 2621·2019-08-30 15:56
閱讀 1173·2019-08-30 14:00
閱讀 2970·2019-08-29 12:37
閱讀 1293·2019-08-28 18:16
閱讀 1683·2019-08-27 10:56
閱讀 1050·2019-08-26 17:23