驗證表單輸入
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.
TypeScript和 HTML5 程式設計。
TypeScript and HTML5 programming.
Angular 應用設計的基本概念。
Fundamental concepts of Angular application design.
Basics of either Template-driven Forms or Reactive Forms.
要獲取這裡用講解表單驗證的響應式表單和範本驅動表單的完整範例程式碼。請執行
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
:
<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 驗證屬性:required
和minlength
。它還帶有一個自訂的驗證器指令forbiddenName
。要了解更多資訊,參閱自訂驗證器一節。The
<input>
element carries the HTML validation attributes:required
andminlength
. It also carries a custom validator directive,forbiddenName
. For more information, see the Custom validators section.#name="ngModel"
把NgModel
匯出成了一個名叫name
的區域性變數。NgModel
把自己控制的FormControl
實例的屬性映射出去,讓你能在範本中檢查控制元件的狀態,比如valid
和dirty
。要了解完整的控制元件屬性,參閱 API 參考手冊中的AbstractControl。#name="ngModel"
exportsNgModel
into a local variable calledname
.NgModel
mirrors many of the properties of its underlyingFormControl
instance, so you can use this in the template to check for control states such asvalid
anddirty
. 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 messagedivs
but only if thename
is invalid and the control is eitherdirty
ortouched
.每個巢狀的
<div>
為其中一個可能出現的驗證錯誤顯示一條自訂訊息。比如required
、minlength
和forbiddenName
。Each nested
<div>
can present a custom message for one of the possible validation errors. There are messages forrequired
,minlength
, andforbiddenName
.
為防止驗證程式在使用者有機會編輯表單之前就顯示錯誤,你應該檢查控制元件的 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 aFormControl
.非同步驗證器 :這些非同步函式接受一個控制元件實例並返回一個 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 aFormControl
.
出於效能方面的考慮,只有在所有同步驗證器都透過之後,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.
在範本驅動表單中用作屬性的那些內建驗證器,比如 required
和 minlength
,也都可以作為 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.
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.required
和 Validators.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.
<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.
/** 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
.
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.
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.
@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:
<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.
.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()
});
注意,name
和 alterEgo
是兄弟控制元件。要想在單個自訂驗證器中計算這兩個控制元件,你就必須在它們共同的祖先控制元件中執行驗證: 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.
/** 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.
該驗證器透過呼叫 FormGroup
的 get 方法來檢索這些子控制元件,然後比較 name
和 alterEgo
控制元件的值。
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.
<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.
@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.
<form #heroForm="ngForm" appIdentityRevealed>
為了提供更好的使用者體驗,當表單無效時,我們要顯示一個恰當的錯誤資訊。
To provide better user experience, we show an appropriate error message when the form is invalid.
<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
非同步驗證器實現了 AsyncValidatorFn
和 AsyncValidator
介面。它們與其同步版本非常相似,但有以下不同之處。
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)。要把無盡的可觀察物件轉換成有盡的,可以在管道中加入過濾運算子,比如
first
、last
、take
或takeUntil
。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
, ortakeUntil
.
非同步驗證在同步驗證完成後才會發生,並且只有在同步驗證成功時才會執行。如果更基本的驗證方法已經發現了無效輸入,那麼這種檢查順序就可以讓表單避免使用昂貴的非同步驗證流程(例如 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
把任務委託給 HeroesService
的 isAlterEgoTaken()
方法,並傳入當前控制元件的值。這時候,該控制元件會被標記為 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
(預設值)改成 submit
或 blur
來推遲表單驗證的更新時機。
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'});