填寫這份《一分鐘調查》,幫我們(開發組)做得更好!去填寫Home

驗證表單輸入

Validating form input

透過驗證使用者輸入的準確性和完整性,可以提高整體的資料品質。該頁面顯示瞭如何從 UI 驗證使用者輸入,以及如何在響應式表單和範本驅動表單中顯示有用的驗證訊息。

You can improve overall data quality by validating user input for accuracy and completeness. This page shows how to validate user input from the UI and display useful validation messages, in both reactive and template-driven forms.

先決條件

Prerequisites

在閱讀表單驗證之前,你應該對這些內容有一個基本的瞭解。

Before reading about form validation, you should have a basic understanding of the following.

要獲取這裡用講解表單驗證的響應式表單和範本驅動表單的完整範例程式碼。請執行現場演練 / 下載範例

Get the complete example code for the reactive and template-driven forms used here to illustrate form validation. Run the現場演練 / 下載範例.

在範本驅動表單中驗證輸入

Validating input in template-driven forms

為了往範本驅動表單中新增驗證機制,你要新增一些驗證屬性,就像原生的 HTML 表單驗證器。 Angular 會用指令來匹配這些具有驗證功能的指令。

To add validation to a template-driven form, you add the same validation attributes as you would with native HTML form validation. Angular uses directives to match these attributes with validator functions in the framework.

每當表單控制元件中的值發生變化時,Angular 就會進行驗證,並產生一個驗證錯誤的列表(對應著 INVALID 狀態)或者 null(對應著 VALID 狀態)。

Every time the value of a form control changes, Angular runs validation and generates either a list of validation errors that results in an INVALID status, or null, which results in a VALID status.

你可以透過把 ngModel 匯出成區域性範本變數來檢視該控制元件的狀態。 比如下面這個例子就把 NgModel 匯出成了一個名叫 name 的變數:

You can then inspect the control's state by exporting ngModel to a local template variable. The following example exports NgModel into a variable called name:

template/hero-form-template.component.html (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>
    

注意這個例子講解的如下特性。

Notice the following features illustrated by the example.

  • <input> 元素帶有一些 HTML 驗證屬性:requiredminlength。它還帶有一個自訂的驗證器指令 forbiddenName。要了解更多資訊,參閱自訂驗證器一節。

    The <input> element carries the HTML validation attributes: required and minlength. It also carries a custom validator directive, forbiddenName. For more information, see the Custom validators section.

  • #name="ngModel"NgModel 匯出成了一個名叫 name 的區域性變數。NgModel 把自己控制的 FormControl 實例的屬性映射出去,讓你能在範本中檢查控制元件的狀態,比如 validdirty。要了解完整的控制元件屬性,參閱 API 參考手冊中的AbstractControl

    #name="ngModel" exports NgModel into a local variable called name. NgModel mirrors many of the properties of its underlying FormControl instance, so you can use this in the template to check for control states such as valid and dirty. For a full list of control properties, see the AbstractControl API reference.

    • <div> 元素的 *ngIf 展示了一組巢狀的訊息 div,但是只在有“name”錯誤和控制器為 dirty 或者 touched 時才出現。

      The *ngIf on the <div> element reveals a set of nested message divs but only if the name is invalid and the control is either dirty or touched.

    • 每個巢狀的 <div> 為其中一個可能出現的驗證錯誤顯示一條自訂訊息。比如 requiredminlengthforbiddenName

      Each nested <div> can present a custom message for one of the possible validation errors. There are messages for required, minlength, and forbiddenName.

為防止驗證程式在使用者有機會編輯表單之前就顯示錯誤,你應該檢查控制元件的 dirty 狀態或 touched 狀態。

To prevent the validator from displaying errors before the user has a chance to edit the form, you should check for either the dirty or touched states in a control.

  • 當用戶在被監視的欄位中修改該值時,控制元件就會被標記為 dirty(髒)。

    When the user changes the value in the watched field, the control is marked as "dirty".

  • 當用戶的表單控制元件失去焦點時,該控制元件就會被標記為 touched(已接觸)。

    When the user blurs the form control element, the control is marked as "touched".

在響應式表單中驗證輸入

Validating input in reactive forms

在響應式表單中,事實之源是其元件類別。不應該透過範本上的屬性來新增驗證器,而應該在元件類別中直接把驗證器函式新增到表單控制元件模型上(FormControl)。然後,一旦控制元件發生了變化,Angular 就會呼叫這些函式。

In a reactive form, the source of truth is the component class. Instead of adding validators through attributes in the template, you add validator functions directly to the form control model in the component class. Angular then calls these functions whenever the value of the control changes.

驗證器(Validator)函式

Validator functions

驗證器函式可以是同步函式,也可以是非同步函式。

Validator functions can be either synchronous or asynchronous.

  • 同步驗證器:這些同步函式接受一個控制元件實例,然後返回一組驗證錯誤或 null。你可以在實例化一個 FormControl 時把它作為建構函式的第二個引數傳進去。

    Sync validators: Synchronous functions that take a control instance and immediately return either a set of validation errors or null. You can pass these in as the second argument when you instantiate a FormControl.

  • 非同步驗證器 :這些非同步函式接受一個控制元件實例並返回一個 Promise 或 Observable,它稍後會發出一組驗證錯誤或 null。在實例化 FormControl 時,可以把它們作為第三個引數傳入。

    Async validators: Asynchronous functions that take a control instance and return a Promise or Observable that later emits a set of validation errors or null. You can pass these in as the third argument when you instantiate a FormControl.

出於效能方面的考慮,只有在所有同步驗證器都透過之後,Angular 才會執行非同步驗證器。當每一個非同步驗證器都執行完之後,才會設定這些驗證錯誤。

For performance reasons, Angular only runs async validators if all sync validators pass. Each must complete before errors are set.

內建驗證器函式

Built-in validator functions

你可以選擇編寫自己的驗證器函式,也可以使用 Angular 的一些內建驗證器。

You can choose to write your own validator functions, or you can use some of Angular's built-in validators.

在範本驅動表單中用作屬性的那些內建驗證器,比如 requiredminlength,也都可以作為 Validators 類別中的函式使用。關於內建驗證器的完整列表,參閱 API 參考手冊中的驗證器部分。

The same built-in validators that are available as attributes in template-driven forms, such as required and minlength, are all available to use as functions from the Validators class. For a full list of built-in validators, see the Validators API reference.

要想把這個英雄表單改造成一個響應式表單,你還是要用那些內建驗證器,但這次改為用它們的函式形態。參閱下面的例子。

To update the hero form to be a reactive form, you can use some of the same built-in validators—this time, in function form, as in the following example.

reactive/hero-form-reactive.component.ts (validator functions)
      
      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'); }
    

在這個例子中,name 控制元件設定了兩個內建驗證器 - Validators.requiredValidators.minLength(4) 以及一個自訂驗證器 forbiddenNameValidator。(欲知詳情,請參閱下面的自訂驗證器部分。)

In this example, the name control sets up two built-in validators—Validators.required and Validators.minLength(4)—and one custom validator, forbiddenNameValidator. (For more details see custom validators below.)

所有這些驗證器都是同步的,所以它們作為第二個引數傳遞。注意,你可以透過把這些函式放到一個數組中傳入來支援多個驗證器。

All of these validators are synchronous, so they are passed as the second argument. Notice that you can support multiple validators by passing the functions in as an array.

這個例子還添加了一些 getter 方法。在響應式表單中,你通常會透過它所屬的控制元件組(FormGroup)的 get 方法來訪問表單控制元件,但有時候為範本定義一些 getter 作為簡短形式。

This example also adds a few getter methods. In a reactive form, you can always access any form control through the get method on its parent group, but sometimes it's useful to define getters as shorthand for the template.

如果你到範本中找到 name 輸入框,就會發現它和範本驅動的例子很相似。

If you look at the template for the name input again, it is fairly similar to the template-driven example.

reactive/hero-form-reactive.component.html (name with error msg)
      
      <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>
    

這個表單與範本驅動的版本不同,它不再匯出任何指令。相反,它使用元件類別中定義的 name 讀取器(getter)。

This form differs from the template-driven version in that it no longer exports any directives. Instead, it uses the name getter defined in the component class.

請注意,required 屬性仍然出現在範本中。雖然它對於驗證來說不是必須的,但為了無障礙性,還是應該保留它。

Notice that the required attribute is still present in the template. Although it's not necessary for validation, it should be retained to for accessibility purposes.

定義自訂驗證器

Defining custom validators

內建的驗證器並不是總能精確匹配應用中的用例,因此有時你需要建立一個自訂驗證器。

The built-in validators don't always match the exact use case of your application, so you sometimes need to create a custom validator.

考慮前面的響應式式表單中forbiddenNameValidator 函式。該函式的定義如下。

Consider the forbiddenNameValidator function from previous reactive-form examples. Here's what the definition of that function looks like.

shared/forbidden-name.directive.ts (forbiddenNameValidator)
      
      /** 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;
  };
}
    

這個函式實際上是一個工廠,它接受一個用來檢測指定名字是否已被禁用的正則表示式,並返回一個驗證器函式。

The function is actually a factory that takes a regular expression to detect a specific forbidden name and returns a validator function.

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

In this sample, the forbidden name is "bob", so the validator will reject any hero name containing "bob". Elsewhere it could reject "alice" or any name that the configuring regular expression matches.

forbiddenNameValidator 工廠函式返回配置好的驗證器函式。 該函式接受一個 Angular 控制器物件,並在控制器值有效時返回 null,或無效時返回驗證錯誤物件。 驗證錯誤物件通常有一個名為驗證祕鑰(forbiddenName)的屬性。其值為一個任意詞典,你可以用來插入錯誤資訊({name})。

The forbiddenNameValidator factory returns the configured validator function. That function takes an Angular control object and returns either null if the control value is valid or a validation error object. The validation error object typically has a property whose name is the validation key, 'forbiddenName', and whose value is an arbitrary dictionary of values that you could insert into an error message, {name}.

自訂非同步驗證器和同步驗證器很像,只是它們必須返回一個稍後會輸出 null 或“驗證錯誤物件”的承諾(Promise)或可觀察物件,如果是可觀察物件,那麼它必須在某個時間點被完成(complete),那時候這個表單就會使用它輸出的最後一個值作為驗證結果。(譯註:HTTP 服務是自動完成的,但是某些自訂的可觀察物件可能需要手動呼叫 complete 方法)

Custom async validators are similar to sync validators, but they must instead return a Promise or observable that later emits null or a validation error object. In the case of an observable, the observable must complete, at which point the form uses the last value emitted for validation.

把自訂驗證器新增到響應式表單中

Adding custom validators to reactive forms

在響應式表單中,透過直接把該函式傳給 FormControl 來新增自訂驗證器。

In reactive forms, add a custom validator by passing the function directly to the FormControl.

reactive/hero-form-reactive.component.ts (validator functions)
      
      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)
});
    

為範本驅動表單中新增自訂驗證器

Adding custom validators to template-driven forms

在範本驅動表單中,要為範本新增一個指令,該指令包含了 validator 函式。例如,對應的 ForbiddenValidatorDirective 用作 forbiddenNameValidator 的包裝器。

In template-driven forms, add a directive to the template, where the directive wraps the validator function. For example, the corresponding ForbiddenValidatorDirective serves as a wrapper around the forbiddenNameValidator.

Angular 在驗證過程中會識別出該指令的作用,因為該指令把自己註冊成了 NG_VALIDATORS 提供者,如下例所示。NG_VALIDATORS 是一個帶有可擴充套件驗證器集合的預定義提供者。

Angular recognizes the directive's role in the validation process because the directive registers itself with the NG_VALIDATORS provider, as shown in the following example. NG_VALIDATORS is a predefined provider with an extensible collection of validators.

shared/forbidden-name.directive.ts (providers)
      
      providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]
    

然後該指令類別實現了 Validator 介面,以便它能簡單的與 Angular 表單整合在一起。這個指令的其餘部分有助於你理解它們是如何協作的:

The directive class then implements the Validator interface, so that it can easily integrate with Angular forms. Here is the rest of the directive to help you get an idea of how it all comes together.

shared/forbidden-name.directive.ts (directive)
      
      @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 選擇器新增到輸入框上就可以啟用這個驗證器了。比如:

Once the ForbiddenValidatorDirective is ready, you can add its selector, appForbiddenName, to any input element to activate it. For example:

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

注意,自訂驗證指令是用 useExisting 而不是 useClass 來實例化的。註冊的驗證程式必須是 ForbiddenValidatorDirective 實例本身 - 表單中的實例,也就是表單中 forbiddenName 屬性被繫結到了"bob"的那個。

Notice that the custom validation directive is instantiated with useExisting rather than useClass. The registered validator must be this instance of the ForbiddenValidatorDirective—the instance in the form with its forbiddenName property bound to “bob".

如果用 useClass 來代替 useExisting,就會註冊一個新的類別實例,而它是沒有 forbiddenName 的。

If you were to replace useExisting with useClass, then you’d be registering a new class instance, one that doesn’t have a forbiddenName.

表示控制元件狀態的 CSS 類別

Control status CSS classes

Angular 會自動把很多控制元件屬性作為 CSS 類別對映到控制元件所在的元素上。你可以使用這些類別來根據表單狀態給表單控制元件元素新增樣式。目前支援下列類別:

Angular automatically mirrors many control properties onto the form control element as CSS classes. You can use these classes to style form control elements according to the state of the form. The following classes are currently supported.

  • .ng-valid
  • .ng-invalid
  • .ng-pending
  • .ng-pristine
  • .ng-dirty
  • .ng-untouched
  • .ng-touched

在下面的例子中,這個英雄表單使用 .ng-valid.ng-invalid 來設定每個表單控制元件的邊框顏色。

In the following example, the hero form uses the .ng-valid and .ng-invalid classes to set the color of each form control's border.

forms.css (status classes)
      
      .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;
}
    

跨欄位交叉驗證

Cross-field validation

跨欄位交叉驗證器是一種自訂驗證器,可以對表單中不同欄位的值進行比較,並針對它們的組合進行接受或拒絕。例如,你可能有一個提供互不相容選項的表單,以便讓使用者選擇 A 或 B,而不能兩者都選。某些欄位值也可能依賴於其它值;使用者可能只有當選擇了 A 之後才能選擇 B。

A cross-field validator is a custom validator that compares the values of different fields in a form and accepts or rejects them in combination. For example, you might have a form that offers mutually incompatible options, so that if the user can choose A or B, but not both. Some field values might also depend on others; a user might be allowed to choose B only if A is also chosen.

下列交叉驗證的例子說明了如何進行如下操作:

The following cross validation examples show how to do the following:

  • 根據兩個兄弟控制元件的值驗證響應式表單或範本驅動表單的輸入,

    Validate reactive or template-based form input based on the values of two sibling controls,

  • 當用戶與表單互動過,且驗證失敗後,就會顯示描述性的錯誤資訊。

    Show a descriptive error message after the user interacted with the form and the validation failed.

這些例子使用了交叉驗證,以確保英雄們不會透過填寫 Hero 表單來暴露自己的真實身份。驗證器會透過檢查英雄的名字和第二人格是否匹配來做到這一點。

The examples use cross-validation to ensure that heroes do not reveal their true identities by filling out the Hero Form. The validators do this by checking that the hero names and alter egos do not match.

為響應式表單新增交叉驗證

Adding cross-validation to reactive forms

該表單具有以下結構:

The form has the following structure:

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

注意,namealterEgo 是兄弟控制元件。要想在單個自訂驗證器中計算這兩個控制元件,你就必須在它們共同的祖先控制元件中執行驗證: FormGroup。你可以在 FormGroup 中查詢它的子控制元件,從而讓你能比較它們的值。

Notice that the name and alterEgo are sibling controls. To evaluate both controls in a single custom validator, you must perform the validation in a common ancestor control: the FormGroup. You query the FormGroup for its child controls so that you can compare their values.

要想給 FormGroup 新增驗證器,就要在建立時把一個新的驗證器傳給它的第二個引數。

To add a validator to the FormGroup, pass the new validator in as the second argument on creation.

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

驗證器的程式碼如下。

The validator code is as follows.

shared/identity-revealed.directive.ts
      
      /** 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;
};
    

這個 identity 驗證器實現了 ValidatorFn 介面。它接收一個 Angular 表單控制元件物件作為引數,當表單有效時,它返回一個 null,否則返回 ValidationErrors 物件。

The identity validator implements the ValidatorFn interface. It takes an Angular control object as an argument and returns either null if the form is valid, or ValidationErrors otherwise.

該驗證器透過呼叫 FormGroupget 方法來檢索這些子控制元件,然後比較 namealterEgo 控制元件的值。

The validator retrieves the child controls by calling the FormGroup's get method, then compares the values of the name and alterEgo controls.

如果值不匹配,則 hero 的身份保持祕密,兩者都有效,且 validator 返回 null。如果匹配,就說明英雄的身份已經暴露了,驗證器必須透過返回一個錯誤物件來把這個表單標記為無效的。

If the values do not match, the hero's identity remains secret, both are valid, and the validator returns null. If they do match, the hero's identity is revealed and the validator must mark the form as invalid by returning an error object.

為了提供更好的使用者體驗,當表單無效時,範本還會顯示一條恰當的錯誤資訊。

To provide better user experience, the template shows an appropriate error message when the form is invalid.

reactive/hero-form-template.component.html
      
      <div *ngIf="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)" class="cross-validation-error-message alert alert-danger">
    Name cannot match alter ego.
</div>
    

如果 FormGroup 中有一個由 identityRevealed 驗證器返回的交叉驗證錯誤,*ngIf 就會顯示錯誤,但只有當該使用者已經與表單進行過互動的時候才顯示。

This *ngIf displays the error if the FormGroup has the cross validation error returned by the identityRevealed validator, but only if the user has finished interacting with the form.

為範本驅動表單新增交叉驗證

Adding cross-validation to template-driven forms

對於範本驅動表單,你必須建立一個指令來包裝驗證器函式。你可以使用NG_VALIDATORS 令牌來把該指令提供為驗證器,如下例所示。

For a template-driven form, you must create a directive to wrap the validator function. You provide that directive as the validator using the NG_VALIDATORS token, as shown in the following example.

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

你必須把這個新指令新增到 HTML 範本中。由於驗證器必須註冊在表單的最高層,因此下列範本會把該指令放在 form 標籤上。

You must add the new directive to the HTML template. Because the validator must be registered at the highest level in the form, the following template puts the directive on the form tag.

template/hero-form-template.component.html
      
      <form #heroForm="ngForm" appIdentityRevealed>
    

為了提供更好的使用者體驗,當表單無效時,我們要顯示一個恰當的錯誤資訊。

To provide better user experience, we show an appropriate error message when the form is invalid.

template/hero-form-template.component.html
      
      <div *ngIf="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)" class="cross-validation-error-message alert">
    Name cannot match alter ego.
</div>
    

這在範本驅動表單和響應式表單中都是一樣的。

This is the same in both template-driven and reactive forms.

建立非同步驗證器

Creating asynchronous validators

非同步驗證器實現了 AsyncValidatorFnAsyncValidator 介面。它們與其同步版本非常相似,但有以下不同之處。

Asynchronous validators implement the AsyncValidatorFn and AsyncValidator interfaces. These are very similar to their synchronous counterparts, with the following differences.

  • validate() 函式必須返回一個 Promise 或可觀察物件,

    The validate() functions must return a Promise or an observable,

  • 返回的可觀察物件必須是有盡的,這意味著它必須在某個時刻完成(complete)。要把無盡的可觀察物件轉換成有盡的,可以在管道中加入過濾運算子,比如 firstlasttaketakeUntil

    The observable returned must be finite, meaning it must complete at some point. To convert an infinite observable into a finite one, pipe the observable through a filtering operator such as first, last, take, or takeUntil.

非同步驗證在同步驗證完成後才會發生,並且只有在同步驗證成功時才會執行。如果更基本的驗證方法已經發現了無效輸入,那麼這種檢查順序就可以讓表單避免使用昂貴的非同步驗證流程(例如 HTTP 請求)。

Asynchronous validation happens after the synchronous validation, and is performed only if the synchronous validation is successful. This check allows forms to avoid potentially expensive async validation processes (such as an HTTP request) if the more basic validation methods have already found invalid input.

非同步驗證開始之後,表單控制元件就會進入 pending 狀態。你可以檢查控制元件的 pending 屬性,並用它來給出對驗證中的視覺反饋。

After asynchronous validation begins, the form control enters a pending state. You can inspect the control's pending property and use it to give visual feedback about the ongoing validation operation.

一種常見的 UI 模式是在執行非同步驗證時顯示 Spinner(轉輪)。下面的例子展示了如何在範本驅動表單中實現這一點。

A common UI pattern is to show a spinner while the async validation is being performed. The following example shows how to achieve this in a template-driven form.

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

實現自訂非同步驗證器

Implementing a custom async validator

在下面的例子中,非同步驗證器可以確保英雄們選擇了一個尚未採用的第二人格。新英雄不斷湧現,老英雄也會離開,所以無法提前找到可用的人格列表。為了驗證潛在的第二人格條目,驗證器必須啟動一個非同步操作來查詢包含所有在編英雄的中央資料庫。

In the following example, an async validator ensures that heroes pick an alter ego that is not already taken. New heroes are constantly enlisting and old heroes are leaving the service, so the list of available alter egos cannot be retrieved ahead of time. To validate the potential alter ego entry, the validator must initiate an asynchronous operation to consult a central database of all currently enlisted heroes.

下面的程式碼建立了一個驗證器類別 UniqueAlterEgoValidator,它實現了 AsyncValidator 介面。

The following code create the validator class, UniqueAlterEgoValidator, which implements the AsyncValidator interface.

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

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

建構函式中注入了 HeroesService,它定義瞭如下介面。

The constructor injects the HeroesService, which defines the following interface.

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

在真實的應用中,HeroesService 會負責向英雄資料庫發起一個 HTTP 請求,以檢查該第二人格是否可用。 從該驗證器的視角看,此服務的具體實現無關緊要,所以這個例子僅僅針對 HeroesService 介面來寫實現程式碼。

In a real world application, the HeroesService would be responsible for making an HTTP request to the hero database to check if the alter ego is available. From the validator's point of view, the actual implementation of the service is not important, so the example can just code against the HeroesService interface.

當驗證開始的時候,UniqueAlterEgoValidator 把任務委託給 HeroesServiceisAlterEgoTaken() 方法,並傳入當前控制元件的值。這時候,該控制元件會被標記為 pending 狀態,直到 validate() 方法所返回的可觀察物件完成(complete)了。

As the validation begins, the UniqueAlterEgoValidator delegates to the HeroesService isAlterEgoTaken() method with the current control value. At this point the control is marked as pending and remains in this state until the observable chain returned from the validate() method completes.

isAlterEgoTaken() 方法會排程一個 HTTP 請求來檢查第二人格是否可用,並返回 Observable<boolean> 作為結果。validate() 方法透過 map 運算子來對響應物件進行管道化處理,並把它轉換成驗證結果。

The isAlterEgoTaken() method dispatches an HTTP request that checks if the alter ego is available, and returns Observable<boolean> as the result. The validate() method pipes the response through the map operator and transforms it into a validation result.

與任何驗證器一樣,如果表單有效,該方法返回 null,如果無效,則返回 ValidationErrors。這個驗證器使用 catchError 運算子來處理任何潛在的錯誤。在這個例子中,驗證器將 isAlterEgoTaken() 錯誤視為成功的驗證,因為未能發出驗證請求並不一定意味著這個第二人格無效。你也可以用不同的方式處理這種錯誤,比如返回 ValidationError 物件。

The method then, like any validator, returns null if the form is valid, and ValidationErrors if it is not. This validator handles any potential errors with the catchError operator. In this case, the validator treats the isAlterEgoTaken() error as a successful validation, because failure to make a validation request does not necessarily mean that the alter ego is invalid. You could handle the error differently and return the ValidationError object instead.

一段時間過後,這條可觀察物件鏈完成,非同步驗證也就完成了。pending 標誌位也設定為 false,該表單的有效性也已更新。

After some time passes, the observable chain completes and the asynchronous validation is done. The pending flag is set to false, and the form validity is updated.

優化非同步驗證器的效能

Optimizing performance of async validators

預設情況下,所有驗證程式在每次表單值更改後都會執行。對於同步驗證器,這通常不會對應用效能產生明顯的影響。但是,非同步驗證器通常會執行某種 HTTP 請求來驗證控制元件。每次按鍵後排程一次 HTTP 請求都會給後端 API 帶來壓力,應該儘可能避免。

By default, all validators run after every form value change. With synchronous validators, this does not normally have a noticeable impact on application performance. Async validators, however, commonly perform some kind of HTTP request to validate the control. Dispatching an HTTP request after every keystroke could put a strain on the backend API, and should be avoided if possible.

你可以把 updateOn 屬性從 change(預設值)改成 submitblur 來推遲表單驗證的更新時機。

You can delay updating the form validity by changing the updateOn property from change (default) to submit or blur.

使用範本驅動表單時,可以在範本中設定該屬性。

With template-driven forms, set the property in the template.

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

使用響應式表單時,可以在 FormControl 實例中設定該屬性。

With reactive forms, set the property in the FormControl instance.

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