Angular 驗(yàn)證表單輸入

2022-07-06 10:42 更新

驗(yàn)證表單輸入

通過(guò)驗(yàn)證用戶輸入的準(zhǔn)確性和完整性,可以提高整體的數(shù)據(jù)質(zhì)量。該頁(yè)面顯示了如何從 UI 驗(yàn)證用戶輸入,以及如何在響應(yīng)式表單和模板驅(qū)動(dòng)表單中顯示有用的驗(yàn)證消息。

要獲取這里用講解表單驗(yàn)證的響應(yīng)式表單和模板驅(qū)動(dòng)表單的完整范例代碼。請(qǐng)運(yùn)行現(xiàn)場(chǎng)演練 / 下載范例

在模板驅(qū)動(dòng)表單中驗(yàn)證輸入

為了往模板驅(qū)動(dòng)表單中添加驗(yàn)證機(jī)制,你要添加一些驗(yàn)證屬性,就像原生的 HTML 表單驗(yàn)證器一樣。 Angular 會(huì)用指令來(lái)匹配這些具有驗(yàn)證功能的指令。

每當(dāng)表單控件中的值發(fā)生變化時(shí),Angular 就會(huì)進(jìn)行驗(yàn)證,并生成一個(gè)驗(yàn)證錯(cuò)誤的列表(對(duì)應(yīng)著 ?INVALID ?狀態(tài))或者 null(對(duì)應(yīng)著 VALID 狀態(tài))。

你可以通過(guò)把 ?ngModel ?導(dǎo)出成局部模板變量來(lái)查看該控件的狀態(tài)。 比如下面這個(gè)例子就把 ?NgModel ?導(dǎo)出成了一個(gè)名叫 ?name ?的變量:

<input type="text" id="name" name="name" class="form-control"
      required minlength="4" appForbiddenName="bob"
      [(ngModel)]="hero.name" #name="ngModel">

<div *ngIf="name.invalid && (name.dirty || name.touched)"
    class="alert">

  <div *ngIf="name.errors?.['required']">
    Name is required.
  </div>
  <div *ngIf="name.errors?.['minlength']">
    Name must be at least 4 characters long.
  </div>
  <div *ngIf="name.errors?.['forbiddenName']">
    Name cannot be Bob.
  </div>

</div>

注意這個(gè)例子講解的如下特性。

  • ?<input>? 元素帶有一些 HTML 驗(yàn)證屬性:?required ?和 ?minlength?。它還帶有一個(gè)自定義的驗(yàn)證器指令 ?forbiddenName?。
  • ?#name="ngModel"? 把 ?NgModel ?導(dǎo)出成了一個(gè)名叫 ?name ?的局部變量。?NgModel ?把自己控制的 ?FormControl ?實(shí)例的屬性映射出去,讓你能在模板中檢查控件的狀態(tài),比如 ?valid ?和 ?dirty?。
    • ?<div>? 元素的 ?*ngIf? 展示了一組嵌套的消息 ?div?,但是只在有“name”錯(cuò)誤和控制器為 ?dirty ?或者 ?touched ?時(shí)才出現(xiàn)。
    • 每個(gè)嵌套的 ?<div>? 為其中一個(gè)可能出現(xiàn)的驗(yàn)證錯(cuò)誤顯示一條自定義消息。比如 ?required?、?minlength ?和 ?forbiddenName?。
為防止驗(yàn)證程序在用戶有機(jī)會(huì)編輯表單之前就顯示錯(cuò)誤,你應(yīng)該檢查控件的 ?dirty ?狀態(tài)或 ?touched ?狀態(tài)。
  • 當(dāng)用戶在被監(jiān)視的字段中修改該值時(shí),控件就會(huì)被標(biāo)記為 ?dirty?(臟)
  • 當(dāng)用戶的表單控件失去焦點(diǎn)時(shí),該控件就會(huì)被標(biāo)記為 ?touched?(已接觸)

在響應(yīng)式表單中驗(yàn)證輸入

在響應(yīng)式表單中,事實(shí)之源是其組件類。不應(yīng)該通過(guò)模板上的屬性來(lái)添加驗(yàn)證器,而應(yīng)該在組件類中直接把驗(yàn)證器函數(shù)添加到表單控件模型上(?FormControl?)。然后,一旦控件發(fā)生了變化,Angular 就會(huì)調(diào)用這些函數(shù)。

驗(yàn)證器(Validator)函數(shù)

驗(yàn)證器函數(shù)可以是同步函數(shù),也可以是異步函數(shù)。

驗(yàn)證器類型

詳細(xì)信息

同步驗(yàn)證器

這些同步函數(shù)接受一個(gè)控件實(shí)例,然后返回一組驗(yàn)證錯(cuò)誤或 null??梢栽趯?shí)例化一個(gè) FormControl 時(shí)把它作為構(gòu)造函數(shù)的第二個(gè)參數(shù)傳進(jìn)去。

異步驗(yàn)證器

這些異步函數(shù)接受一個(gè)控件實(shí)例并返回一個(gè) Promise 或 Observable,它稍后會(huì)發(fā)出一組驗(yàn)證錯(cuò)誤或 null。在實(shí)例化 FormControl 時(shí),可以把它們作為第三個(gè)參數(shù)傳入。

出于性能方面的考慮,只有在所有同步驗(yàn)證器都通過(guò)之后,Angular 才會(huì)運(yùn)行異步驗(yàn)證器。當(dāng)每一個(gè)異步驗(yàn)證器都執(zhí)行完之后,才會(huì)設(shè)置這些驗(yàn)證錯(cuò)誤。

內(nèi)置驗(yàn)證器函數(shù)

你可以選擇編寫自己的驗(yàn)證器函數(shù),也可以使用 Angular 的一些內(nèi)置驗(yàn)證器。

在模板驅(qū)動(dòng)表單中用作屬性的那些內(nèi)置驗(yàn)證器,比如 ?required ?和 ?minlength?,也都可以作為 ?Validators ?類中的函數(shù)使用。

要想把這個(gè)英雄表單改造成一個(gè)響應(yīng)式表單,還是要用那些內(nèi)置驗(yàn)證器,但這次改為用它們的函數(shù)形態(tài)。參閱下面的例子。

ngOnInit(): void {
  this.heroForm = new FormGroup({
    name: new FormControl(this.hero.name, [
      Validators.required,
      Validators.minLength(4),
      forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
    ]),
    alterEgo: new FormControl(this.hero.alterEgo),
    power: new FormControl(this.hero.power, Validators.required)
  });

}

get name() { return this.heroForm.get('name'); }

get power() { return this.heroForm.get('power'); }

在這個(gè)例子中,?name ?控件設(shè)置了兩個(gè)內(nèi)置驗(yàn)證器 - ?Validators.required? 和 ?Validators.minLength(4)? 以及一個(gè)自定義驗(yàn)證器 ?forbiddenNameValidator?。

所有這些驗(yàn)證器都是同步的,所以它們作為第二個(gè)參數(shù)傳遞。注意,你可以通過(guò)把這些函數(shù)放到一個(gè)數(shù)組中傳入來(lái)支持多個(gè)驗(yàn)證器。

這個(gè)例子還添加了一些 getter 方法。在響應(yīng)式表單中,你通常會(huì)通過(guò)它所屬的控件組(FormGroup)的 ?get ?方法來(lái)訪問(wèn)表單控件,但有時(shí)候?yàn)槟0宥x一些 getter 作為簡(jiǎn)短形式。

如果你到模板中找到 ?name ?輸入框,就會(huì)發(fā)現(xiàn)它和模板驅(qū)動(dòng)的例子很相似。

<input type="text" id="name" class="form-control"
      formControlName="name" required>

<div *ngIf="name.invalid && (name.dirty || name.touched)"
    class="alert alert-danger">

  <div *ngIf="name.errors?.['required']">
    Name is required.
  </div>
  <div *ngIf="name.errors?.['minlength']">
    Name must be at least 4 characters long.
  </div>
  <div *ngIf="name.errors?.['forbiddenName']">
    Name cannot be Bob.
  </div>
</div>

這個(gè)表單與模板驅(qū)動(dòng)的版本不同,它不再導(dǎo)出任何指令。相反,它使用組件類中定義的 ?name ?讀取器(getter)。

請(qǐng)注意,?required ?屬性仍然出現(xiàn)在模板中。雖然它對(duì)于驗(yàn)證來(lái)說(shuō)不是必須的,但為了無(wú)障礙性,還是應(yīng)該保留它。

定義自定義驗(yàn)證器

內(nèi)置的驗(yàn)證器并不是總能精確匹配應(yīng)用中的用例,因此有時(shí)你需要?jiǎng)?chuàng)建一個(gè)自定義驗(yàn)證器。

考慮前面的響應(yīng)式式表單中的 ?forbiddenNameValidator ?函數(shù)。該函數(shù)的定義如下。

/** A hero's name can't match the given regular expression */
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const forbidden = nameRe.test(control.value);
    return forbidden ? {forbiddenName: {value: control.value}} : null;
  };
}

這個(gè)函數(shù)是一個(gè)工廠,它接受一個(gè)用來(lái)檢測(cè)指定名字是否已被禁用的正則表達(dá)式,并返回一個(gè)驗(yàn)證器函數(shù)。

在本例中,禁止的名字是“bob”; 驗(yàn)證器會(huì)拒絕任何帶有“bob”的英雄名字。 在其它地方,只要配置的正則表達(dá)式可以匹配上,它可能拒絕“alice”或者任何其它名字。

?forbiddenNameValidator ?工廠函數(shù)返回配置好的驗(yàn)證器函數(shù)。 該函數(shù)接受一個(gè) Angular 控制器對(duì)象,并在控制器值有效時(shí)返回 null,或無(wú)效時(shí)返回驗(yàn)證錯(cuò)誤對(duì)象。 驗(yàn)證錯(cuò)誤對(duì)象通常有一個(gè)名為驗(yàn)證秘鑰(?forbiddenName?)的屬性。其值為一個(gè)任意詞典,你可以用來(lái)插入錯(cuò)誤信息(?{name}?)。

自定義異步驗(yàn)證器和同步驗(yàn)證器很像,只是它們必須返回一個(gè)稍后會(huì)輸出 null 或“驗(yàn)證錯(cuò)誤對(duì)象”的承諾(Promise)或可觀察對(duì)象,如果是可觀察對(duì)象,那么它必須在某個(gè)時(shí)間點(diǎn)被完成(complete),那時(shí)候這個(gè)表單就會(huì)使用它輸出的最后一個(gè)值作為驗(yàn)證結(jié)果。(譯注:HTTP 服務(wù)是自動(dòng)完成的,但是某些自定義的可觀察對(duì)象可能需要手動(dòng)調(diào)用 complete 方法)

把自定義驗(yàn)證器添加到響應(yīng)式表單中

在響應(yīng)式表單中,通過(guò)直接把該函數(shù)傳給 ?FormControl ?來(lái)添加自定義驗(yàn)證器。

this.heroForm = new FormGroup({
  name: new FormControl(this.hero.name, [
    Validators.required,
    Validators.minLength(4),
    forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
  ]),
  alterEgo: new FormControl(this.hero.alterEgo),
  power: new FormControl(this.hero.power, Validators.required)
});

為模板驅(qū)動(dòng)表單中添加自定義驗(yàn)證器

在模板驅(qū)動(dòng)表單中,要為模板添加一個(gè)指令,該指令包含了 validator 函數(shù)。比如,對(duì)應(yīng)的 ?ForbiddenValidatorDirective ?用作 ?forbiddenNameValidator ?的包裝器。

Angular 在驗(yàn)證過(guò)程中會(huì)識(shí)別出該指令的作用,因?yàn)樵撝噶畎炎约鹤?cè)成了 ?NG_VALIDATORS ?提供者,如下例所示。?NG_VALIDATORS ?是一個(gè)帶有可擴(kuò)展驗(yàn)證器集合的預(yù)定義提供者。

providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]

然后該指令類實(shí)現(xiàn)了 ?Validator ?接口,以便它能簡(jiǎn)單的與 Angular 表單集成在一起。這個(gè)指令的其余部分有助于你理解它們是如何協(xié)作的:

@Directive({
  selector: '[appForbiddenName]',
  providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]
})
export class ForbiddenValidatorDirective implements Validator {
  @Input('appForbiddenName') forbiddenName = '';

  validate(control: AbstractControl): ValidationErrors | null {
    return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control)
                              : null;
  }
}

一旦 ?ForbiddenValidatorDirective ?寫好了,你只要把 ?forbiddenName ?選擇器添加到輸入框上就可以激活這個(gè)驗(yàn)證器了。比如:

<input type="text" id="name" name="name" class="form-control"
      required minlength="4" appForbiddenName="bob"
      [(ngModel)]="hero.name" #name="ngModel">
注意,自定義驗(yàn)證指令是用 ?useExisting ?而不是 ?useClass ?來(lái)實(shí)例化的。注冊(cè)的驗(yàn)證程序必須是 ?ForbiddenValidatorDirective ?實(shí)例本身 - 表單中的實(shí)例,也就是表單中 ?forbiddenName ?屬性被綁定到了"bob"的那個(gè)。
如果用 ?useClass ?來(lái)代替 ?useExisting?,就會(huì)注冊(cè)一個(gè)新的類實(shí)例,而它是沒(méi)有 ?forbiddenName ?的。

表示控件狀態(tài)的 CSS 類

Angular 會(huì)自動(dòng)把很多控件屬性作為 CSS 類映射到控件所在的元素上。你可以使用這些類來(lái)根據(jù)表單狀態(tài)給表單控件元素添加樣式。目前支持下列類:

  • ?.ng-valid ?
  • ?.ng-invalid ?
  • ?.ng-pending ?
  • ?.ng-pristine ?
  • ?.ng-dirty ?
  • ?.ng-untouched ?
  • ?.ng-touched ?
  • ?.ng-submitted? (只對(duì) form 元素添加)

在下面的例子中,這個(gè)英雄表單使用 ?.ng-valid? 和 ?.ng-invalid? 來(lái)設(shè)置每個(gè)表單控件的邊框顏色。

.ng-valid[required], .ng-valid.required  {
  border-left: 5px solid #42A948; /* green */
}

.ng-invalid:not(form)  {
  border-left: 5px solid #a94442; /* red */
}

.alert div {
  background-color: #fed3d3;
  color: #820000;
  padding: 1rem;
  margin-bottom: 1rem;
}

.form-group {
  margin-bottom: 1rem;
}

label {
  display: block;
  margin-bottom: .5rem;
}

select {
  width: 100%;
  padding: .5rem;
}

跨字段交叉驗(yàn)證

跨字段交叉驗(yàn)證器是一種自定義驗(yàn)證器,可以對(duì)表單中不同字段的值進(jìn)行比較,并針對(duì)它們的組合進(jìn)行接受或拒絕。比如,你可能有一個(gè)提供互不兼容選項(xiàng)的表單,以便讓用戶選擇 A 或 B,而不能兩者都選。某些字段值也可能依賴于其它值;用戶可能只有當(dāng)選擇了 A 之后才能選擇 B。

下列交叉驗(yàn)證的例子說(shuō)明了如何進(jìn)行如下操作:

  • 根據(jù)兩個(gè)兄弟控件的值驗(yàn)證響應(yīng)式表單或模板驅(qū)動(dòng)表單的輸入,
  • 當(dāng)用戶與表單交互過(guò),且驗(yàn)證失敗后,就會(huì)顯示描述性的錯(cuò)誤信息。

這些例子使用了交叉驗(yàn)證,以確保英雄們不會(huì)通過(guò)填寫 Hero 表單來(lái)暴露自己的真實(shí)身份。驗(yàn)證器會(huì)通過(guò)檢查英雄的名字和第二人格是否匹配來(lái)做到這一點(diǎn)。

為響應(yīng)式表單添加交叉驗(yàn)證

該表單具有以下結(jié)構(gòu):

const heroForm = new FormGroup({
  'name': new FormControl(),
  'alterEgo': new FormControl(),
  'power': new FormControl()
});

注意,?name ?和 ?alterEgo ?是兄弟控件。要想在單個(gè)自定義驗(yàn)證器中計(jì)算這兩個(gè)控件,你就必須在它們共同的祖先控件中執(zhí)行驗(yàn)證:?FormGroup?。你可以在 ?FormGroup ?中查詢它的子控件,從而讓你能比較它們的值。

要想給 ?FormGroup ?添加驗(yàn)證器,就要在創(chuàng)建時(shí)把一個(gè)新的驗(yàn)證器傳給它的第二個(gè)參數(shù)。

const heroForm = new FormGroup({
  'name': new FormControl(),
  'alterEgo': new FormControl(),
  'power': new FormControl()
}, { validators: identityRevealedValidator });

驗(yàn)證器的代碼如下。

/** A hero's name can't match the hero's alter ego */
export const identityRevealedValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
  const name = control.get('name');
  const alterEgo = control.get('alterEgo');

  return name && alterEgo && name.value === alterEgo.value ? { identityRevealed: true } : null;
};

這個(gè) ?identity ?驗(yàn)證器實(shí)現(xiàn)了 ?ValidatorFn ?接口。它接收一個(gè) Angular 表單控件對(duì)象作為參數(shù),當(dāng)表單有效時(shí),它返回一個(gè) null,否則返回 ?ValidationErrors ?對(duì)象。

該驗(yàn)證器通過(guò)調(diào)用 ?FormGroup ?的 ?get ?方法來(lái)檢索這些子控件,然后比較 ?name ?和 ?alterEgo ?控件的值。

如果值不匹配,則 hero 的身份保持秘密,兩者都有效,且 validator 返回 null。如果匹配,就說(shuō)明英雄的身份已經(jīng)暴露了,驗(yàn)證器必須通過(guò)返回一個(gè)錯(cuò)誤對(duì)象來(lái)把這個(gè)表單標(biāo)記為無(wú)效的。

為了提供更好的用戶體驗(yàn),當(dāng)表單無(wú)效時(shí),模板還會(huì)顯示一條恰當(dāng)?shù)腻e(cuò)誤信息。

<div *ngIf="heroForm.errors?.['identityRevealed'] && (heroForm.touched || heroForm.dirty)" class="cross-validation-error-message alert alert-danger">
    Name cannot match alter ego.
</div>

如果 ?FormGroup ?中有一個(gè)由 ?identityRevealed ?驗(yàn)證器返回的交叉驗(yàn)證錯(cuò)誤,?*ngIf? 就會(huì)顯示錯(cuò)誤,但只有當(dāng)該用戶已經(jīng)與表單進(jìn)行過(guò)交互的時(shí)候才顯示。

為模板驅(qū)動(dòng)表單添加交叉驗(yàn)證

對(duì)于模板驅(qū)動(dòng)表單,你必須創(chuàng)建一個(gè)指令來(lái)包裝驗(yàn)證器函數(shù)。你可以使用?NG_VALIDATORS ?令牌來(lái)把該指令提供為驗(yàn)證器,如下例所示。

@Directive({
  selector: '[appIdentityRevealed]',
  providers: [{ provide: NG_VALIDATORS, useExisting: IdentityRevealedValidatorDirective, multi: true }]
})
export class IdentityRevealedValidatorDirective implements Validator {
  validate(control: AbstractControl): ValidationErrors | null {
    return identityRevealedValidator(control);
  }
}

你必須把這個(gè)新指令添加到 HTML 模板中。由于驗(yàn)證器必須注冊(cè)在表單的最高層,因此下列模板會(huì)把該指令放在 ?form ?標(biāo)簽上。

<form #heroForm="ngForm" appIdentityRevealed>

為了提供更好的用戶體驗(yàn),當(dāng)表單無(wú)效時(shí),我們要顯示一個(gè)恰當(dāng)?shù)腻e(cuò)誤信息。

<div *ngIf="heroForm.errors?.['identityRevealed'] && (heroForm.touched || heroForm.dirty)" class="cross-validation-error-message alert">
    Name cannot match alter ego.
</div>

這在模板驅(qū)動(dòng)表單和響應(yīng)式表單中都是一樣的。

創(chuàng)建異步驗(yàn)證器

異步驗(yàn)證器實(shí)現(xiàn)了 ?AsyncValidatorFn ?和 ?AsyncValidator ?接口。它們與其同步版本非常相似,但有以下不同之處。

  • ?validate()? 函數(shù)必須返回一個(gè) Promise 或可觀察對(duì)象,
  • 返回的可觀察對(duì)象必須是有盡的,這意味著它必須在某個(gè)時(shí)刻完成(complete)。要把無(wú)盡的可觀察對(duì)象轉(zhuǎn)換成有盡的,可以在管道中加入過(guò)濾操作符,比如 ?first?、?last?、?take ?或 ?takeUntil?。

異步驗(yàn)證在同步驗(yàn)證完成后才會(huì)發(fā)生,并且只有在同步驗(yàn)證成功時(shí)才會(huì)執(zhí)行。如果更基本的驗(yàn)證方法已經(jīng)發(fā)現(xiàn)了無(wú)效輸入,那么這種檢查順序就可以讓表單避免使用昂貴的異步驗(yàn)證流程(比如 HTTP 請(qǐng)求)。

異步驗(yàn)證開(kāi)始之后,表單控件就會(huì)進(jìn)入 ?pending ?狀態(tài)??梢詸z查控件的 ?pending ?屬性,并用它來(lái)給出對(duì)驗(yàn)證中的視覺(jué)反饋。

一種常見(jiàn)的 UI 模式是在執(zhí)行異步驗(yàn)證時(shí)顯示 Spinner(轉(zhuǎn)輪)。下面的例子展示了如何在模板驅(qū)動(dòng)表單中實(shí)現(xiàn)這一點(diǎn)。

<input [(ngModel)]="name" #model="ngModel" appSomeAsyncValidator>
<app-spinner *ngIf="model.pending"></app-spinner>

實(shí)現(xiàn)自定義異步驗(yàn)證器

在下面的例子中,異步驗(yàn)證器可以確保英雄們選擇了一個(gè)尚未采用的第二人格。新英雄不斷涌現(xiàn),老英雄也會(huì)離開(kāi),所以無(wú)法提前找到可用的人格列表。為了驗(yàn)證潛在的第二人格條目,驗(yàn)證器必須啟動(dòng)一個(gè)異步操作來(lái)查詢包含所有在編英雄的中央數(shù)據(jù)庫(kù)。

下面的代碼創(chuàng)建了一個(gè)驗(yàn)證器類 ?UniqueAlterEgoValidator?,它實(shí)現(xiàn)了 ?AsyncValidator ?接口。

@Injectable({ providedIn: 'root' })
export class UniqueAlterEgoValidator implements AsyncValidator {
  constructor(private heroesService: HeroesService) {}

  validate(
    control: AbstractControl
  ): Observable<ValidationErrors | null> {
    return this.heroesService.isAlterEgoTaken(control.value).pipe(
      map(isTaken => (isTaken ? { uniqueAlterEgo: true } : null)),
      catchError(() => of(null))
    );
  }
}

構(gòu)造函數(shù)中注入了 ?HeroesService?,它定義了如下接口。

interface HeroesService {
  isAlterEgoTaken: (alterEgo: string) => Observable<boolean>;
}

在真實(shí)的應(yīng)用中,?HeroesService ?會(huì)負(fù)責(zé)向英雄數(shù)據(jù)庫(kù)發(fā)起一個(gè) HTTP 請(qǐng)求,以檢查該第二人格是否可用。 從該驗(yàn)證器的視角看,此服務(wù)的具體實(shí)現(xiàn)無(wú)關(guān)緊要,所以這個(gè)例子僅僅針對(duì) ?HeroesService ?接口來(lái)寫實(shí)現(xiàn)代碼。

當(dāng)驗(yàn)證開(kāi)始的時(shí)候,?UniqueAlterEgoValidator ?把任務(wù)委托給 ?HeroesService ?的 ?isAlterEgoTaken()? 方法,并傳入當(dāng)前控件的值。這時(shí)候,該控件會(huì)被標(biāo)記為 ?pending? 狀態(tài),直到 ?validate()? 方法所返回的可觀察對(duì)象完成(complete)了。

?isAlterEgoTaken()? 方法會(huì)調(diào)度一個(gè) HTTP 請(qǐng)求來(lái)檢查第二人格是否可用,并返回 ?Observable<boolean>? 作為結(jié)果。?validate()? 方法通過(guò) ?map ?操作符來(lái)對(duì)響應(yīng)對(duì)象進(jìn)行管道化處理,并把它轉(zhuǎn)換成驗(yàn)證結(jié)果。

與任何驗(yàn)證器一樣,如果表單有效,該方法返回 ?null?,如果無(wú)效,則返回 ?ValidationErrors?。這個(gè)驗(yàn)證器使用 ?catchError ?操作符來(lái)處理任何潛在的錯(cuò)誤。在這個(gè)例子中,驗(yàn)證器將 ?isAlterEgoTaken()? 錯(cuò)誤視為成功的驗(yàn)證,因?yàn)槲茨馨l(fā)出驗(yàn)證請(qǐng)求并不一定意味著這個(gè)第二人格無(wú)效。你也可以用不同的方式處理這種錯(cuò)誤,比如返回 ?ValidationError ?對(duì)象。

一段時(shí)間過(guò)后,這條可觀察對(duì)象鏈完成,異步驗(yàn)證也就完成了。?pending ?標(biāo)志位也設(shè)置為 ?false?,該表單的有效性也已更新。

將異步驗(yàn)證器添加到響應(yīng)式表單

要以響應(yīng)式表單使用異步驗(yàn)證器,請(qǐng)首先將驗(yàn)證器注入組件類的構(gòu)造函數(shù)。

constructor(private alterEgoValidator: UniqueAlterEgoValidator) {}

然后,將驗(yàn)證器函數(shù)直接傳遞給 ?FormControl ?以應(yīng)用它。

在以下示例中,?UniqueAlterEgoValidator ?的 ?validate ?函數(shù)將其傳遞給控件的 ?asyncValidators ?選項(xiàng)并將其綁定到注入到 ?HeroFormReactiveComponent ?中的 ?UniqueAlterEgoValidator ?實(shí)例,最終將其應(yīng)用于 ?alterEgoControl?。?asyncValidators ?的值可以是單個(gè)異步驗(yàn)證器函數(shù),也可以是函數(shù)數(shù)組。

const alterEgoControl = new FormControl('', {
  asyncValidators: [this.alterEgoValidator.validate.bind(this.alterEgoValidator)],
  updateOn: 'blur'
});

將異步驗(yàn)證器添加到模板驅(qū)動(dòng)表單

要在模板驅(qū)動(dòng)表單中使用異步驗(yàn)證器,請(qǐng)創(chuàng)建一個(gè)新指令并在其上注冊(cè) ?NG_ASYNC_VALIDATORS ?提供者。

在下面的示例中,該指令注入包含實(shí)際驗(yàn)證邏輯的 ?UniqueAlterEgoValidator ?類,并在應(yīng)該進(jìn)行驗(yàn)證時(shí)由 Angular 觸發(fā)的 ?validate ?函數(shù)中調(diào)用它。

@Directive({
  selector: '[appUniqueAlterEgo]',
  providers: [
    {
      provide: NG_ASYNC_VALIDATORS,
      useExisting: forwardRef(() => UniqueAlterEgoValidatorDirective),
      multi: true
    }
  ]
})
export class UniqueAlterEgoValidatorDirective implements AsyncValidator {
  constructor(private validator: UniqueAlterEgoValidator) {}

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

然后,與使用同步驗(yàn)證器一樣,將指令的選擇器添加到輸入以激活它。

<input type="text"
         id="alterEgo"
         name="alterEgo"
         #alterEgo="ngModel"
         [(ngModel)]="hero.alterEgo"
         [ngModelOptions]="{ updateOn: 'blur' }"
         appUniqueAlterEgo>

優(yōu)化異步驗(yàn)證器的性能

默認(rèn)情況下,所有驗(yàn)證程序在每次表單值更改后都會(huì)運(yùn)行。對(duì)于同步驗(yàn)證器,這通常不會(huì)對(duì)應(yīng)用性能產(chǎn)生明顯的影響。但是,異步驗(yàn)證器通常會(huì)執(zhí)行某種 HTTP 請(qǐng)求來(lái)驗(yàn)證控件。每次按鍵后調(diào)度一次 HTTP 請(qǐng)求都會(huì)給后端 API 帶來(lái)壓力,應(yīng)該盡可能避免。

你可以把 ?updateOn ?屬性從 ?change?(默認(rèn)值)改成 ?submit ?或 ?blur ?來(lái)推遲表單驗(yàn)證的更新時(shí)機(jī)。

使用模板驅(qū)動(dòng)表單時(shí),可以在模板中設(shè)置該屬性。

<input [(ngModel)]="name" [ngModelOptions]="{updateOn: 'blur'}">

使用響應(yīng)式表單時(shí),可以在 ?FormControl ?實(shí)例中設(shè)置該屬性。

new FormControl('', {updateOn: 'blur'});

與原生 HTML 表單驗(yàn)證器交互

默認(rèn)情況下,Angular 通過(guò)在 ?<form>? 元素上添加 ?novalidate ?屬性來(lái)禁用原生 HTML 表單驗(yàn)證,并使用指令將這些屬性與框架中的驗(yàn)證器函數(shù)相匹配。如果你想將原生驗(yàn)證與基于 Angular 的驗(yàn)證結(jié)合使用,你可以使用 ?ngNativeValidate ?指令來(lái)重新啟用它。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)