Angular如何在模板驅動表單中自定義校驗器

引言

模板驅動表單相比較響應式表單可以少更少的代碼做同樣的事情靶端,可也損失了自由度更易測試,當然很多人并不在乎啦显歧。

所以我相信很多人在編寫Angular不自由自主去更傾向于模板驅動表單的寫法占遥。

表單最核心的是校驗體驗喊儡,在Angular中簡直就是發(fā)揮到了極致颊糜,比如:requiredmin秃踩、max衬鱼、pattern 等,這些原本是HTML DOM元素中的表述憔杨,而Angular默認實現(xiàn)了一整套的校驗指令鸟赫,比如:required 對應 RequiredValidator

然后很多時候我們需要一些特殊的校驗消别,比如:數(shù)據(jù)比較抛蚤、遠程校驗等。那在模板驅動表單風格中我們要如何優(yōu)雅的實現(xiàn)這樣一個校驗器呢寻狂?

一岁经、Angular是如何校驗?

一般在編寫一個手機文本框可能是這樣:

<input [(ngModel)]="user.mobile" #mobile="ngModel" autocomplete="off" type="tel" class="form-control" name="mobile" required maxlength="11">
<div *ngIf="mobile.errors">
    <p *ngIf="mobile.errors.required">手機號必填</p>
    <p *ngIf="mobile.errors.pattern">手機號格式不正確</p>
</div>

以上幾行很友好的實現(xiàn)從必填項荆虱、格式進行校驗蒿偎,而這一切都是依靠 [(ngModel)] 統(tǒng)一采集,得以只需要利用一個模板引用變量訪問到每個校驗指令的錯誤信息怀读。

1诉位、[(ngModel)] 到底做了什么?

在解析這個問題前需要先了解一下 RequiredValidator 是如何定義的菜枷。

@Directive({
  providers: [{
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => RequiredValidator),
      multi: true
    }]
})
export class RequiredValidator {}

只看最核心向 NG_VALIDATORS 標識符注冊一個 RequiredValidator 指令苍糠。這樣就可以使 ngModel 指令中注入 NG_VALIDATORS 后就能得到這個指令對象。

ngModel 我把它簡化了一下:

export class NgModel extends NgControl {
    constructor(@Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>) {}
    
    get validator(): ValidatorFn|null {
        // 各種校驗并返回結果
    }
}

有關更多ng_model.ts可以深入閱讀源代碼啤誊。

Angular會在每一次表單值變更時岳瞭,對所有的表單中已經(jīng)安裝的校驗器進行一次遍歷。

二蚊锹、編寫一個校驗器

誠如 required 校驗器一樣瞳筏,依然是把自定義校驗器掛到 NG_VALIDATORS 當中。假如我們希望手機文本框只能輸入 159 開頭的一個校驗器牡昆。

定義Directive

@Directive({
    selector: '[user-mobile]',
    exportAs: 'userMobile',
    providers: [{
        provide: NG_VALIDATORS,
        useExisting: forwardRef(() => UserMobileDirective),
        multi: true
    }]
})
export class UserMobileDirective {}

一個非常普通的指令定義方法姚炕,只是多了一個將 UserMobileDirective 注冊到 NG_VALIDATORS 標識符當中而已。別問我為什么丢烘,一種約定柱宦。

export class UserMobileDirective implements Validator {
    validate(c: AbstractControl): { [key: string]: any; } {
        let value: string = c.value || '';
        if (!value.startsWith('159')) {
            return {
                mobile: {
                    msg: '手機號必須是159開頭',
                    actualValue: value
                }
            };
        }
        return null;
    }
}

只需要實現(xiàn) Validator 接口的 validate 方法即可。

c 中獲取DOM值播瞳,當遇到非 159 開頭時掸刊,返回一個用于表述消息的對象即可,否則返回一個 null赢乓。這個對象會被統(tǒng)一采集在 ngModel.errors 對象下面忧侧。故而石窑,只需要在DOM元素加上 user-mobile 指令即可。

<input user-mobile [(ngModel)]="user.mobile" #mobile="ngModel" autocomplete="off" type="tel" class="form-control" name="mobile" id="mobile" required maxlength="11">
<div *ngIf="mobile.errors">
    <p *ngIf="mobile.errors.required">手機號必填</p>
    <p *ngIf="mobile.errors.mobile">{{mobile.errors.mobile.msg}}</p>
</div>

接口還包括一個 registerOnValidatorChange 可選方法苍柏,當某些其它外部屬性的變更時尼斧,允許重新手動觸發(fā)校驗。

三试吁、異步校驗器

如果說用戶手機校驗器需要檢查手機是否為黑名單的情況下惕橙,正常黑名單數(shù)據(jù)都存在遠程當中膝昆。這樣情況下需要發(fā)送HTTP請求辟汰,而這一過程就是異步廓潜。

Angular針對這類異步校驗有獨立的另一個標識符,即:NG_ASYNC_VALIDATORS余耽,而其它代碼都是相通的缚柏。

@Directive({
    selector: '[user-async]',
    exportAs: 'userAsync',
    providers: [{
        provide: NG_ASYNC_VALIDATORS,
        useExisting: forwardRef(() => UserAsyncDirective),
        multi: true
    }]
})
export class UserAsyncDirective implements Validator {
    validate(c: AbstractControl): Observable<any> {
        return c.valueChanges
                // 去抖
                .debounceTime(300)
                // 抑制重復值
                .distinctUntilChanged()
                // 1、可以使用flatMap進行遠程校驗
                // .flatMap(value => value)
                // 2碟贾、本地模擬判斷
                .map((value: string) => {
                    if ([ '15900000001', '15900000002' ].includes(value)) {
                        return {
                            mobile: {
                                msg: '手機號為黑名',
                                actualValue: value
                            }
                        }
                    }
                    return null;
                })
                .first();        
    }
}

除了 NG_ASYNC_VALIDATORS 核心的結構完全沒有變動币喧。

而對于 validate 方法返回的是一個 Observable 類型,利用對 valueChanges 的訂閱可以制作一些像去抖動作袱耽。

而最后必須使用 first() 做為結尾杀餐,原因每一次校驗,對于結果而言只允許一個朱巨。

結論

本章介紹的是如何對模板驅動表單創(chuàng)建自定義校驗器史翘,它相比較響應式表單自定義校驗器略為復雜一些。但是實際運用中冀续,我們不應該只為某個構建表單風格做一種自定義校驗器琼讽,應該二者是共存的。

比如上面 159 開頭的示例洪唐。更合理的編寫方式應該是將校驗邏輯獨立:

export class MyValidators {
    static checkMobile(value: string): ValidationErrors|null {
        return !value.startsWith('159') ? { mobile: { msg: '手機號必須是159開頭' } } : null;
    }
}

// 校驗器類
export class UserMobileDirective implements Validator {
    validate(c: AbstractControl): { [key: string]: any; } {
        let value: string = c.value || '';
        return MyValidators.checkMobile(value);
    }
}

這樣钻蹬,同一個校驗器,不管是模板驅動表單還是響應式表單凭需,都能是通用的脉让。

Happy coding!

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市功炮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌术唬,老刑警劉巖薪伏,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異粗仓,居然都是意外死亡嫁怀,警方通過查閱死者的電腦和手機设捐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來塘淑,“玉大人萝招,你說我怎么就攤上這事〈孓啵” “怎么了槐沼?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長捌治。 經(jīng)常有香客問我岗钩,道長,這世上最難降的妖魔是什么肖油? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任兼吓,我火速辦了婚禮,結果婚禮上森枪,老公的妹妹穿的比我還像新娘视搏。我一直安慰自己,他們只是感情好县袱,可當我...
    茶點故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布浑娜。 她就那樣靜靜地躺著,像睡著了一般显拳。 火紅的嫁衣襯著肌膚如雪棚愤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天杂数,我揣著相機與錄音宛畦,去河邊找鬼。 笑死揍移,一個胖子當著我的面吹牛次和,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播那伐,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼踏施,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了罕邀?” 一聲冷哼從身側響起畅形,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎诉探,沒想到半個月后日熬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡肾胯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年竖席,在試婚紗的時候發(fā)現(xiàn)自己被綠了耘纱。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,769評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡毕荐,死狀恐怖束析,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情憎亚,我是刑警寧澤员寇,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站虽填,受9級特大地震影響丁恭,放射性物質發(fā)生泄漏。R本人自食惡果不足惜斋日,卻給世界環(huán)境...
    茶點故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一牲览、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧恶守,春花似錦第献、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至衫樊,卻和暖如春飒赃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背科侈。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工载佳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人臀栈。 一個月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓蔫慧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親权薯。 傳聞我的和親對象是個殘疾皇子姑躲,可洞房花燭夜當晚...
    茶點故事閱讀 45,781評論 2 361

推薦閱讀更多精彩內(nèi)容