Angular 爲咱們提供了多種方式和 API,進行表單驗證。接下來咱們將介紹如何利用 AbstractControl
實現 FormGroup
的驗證。文章中會涉及 FormGroup
、FormControl
和 FormBuilder
的相關知識,所以建議不瞭解上述知識的讀者,閱讀本文前先閱讀 Angular 4.x Reactive Forms 這篇文章。html
What is a FormGroupreact
FormBuilder/FormGroup source codetypescript
AbstractControlsegmentfault
Custom validation propertiesangular2
Custom validation Object hook異步
咱們先來看一下 Angular 4.x Reactive Forms 中,使用 FormBuilder
的示例:async
signup-form.component.ts函數
import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { User } from './signup.interface'; @Component({...}) export class SignupFormComponent implements OnInit { user: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() { this.user = this.fb.group({ name: ['', [Validators.required, Validators.minLength(2)]], account: this.fb.group({ email: ['', Validators.required], confirm: ['', Validators.required] }) }); } onSubmit({ value, valid }: { value: User, valid: boolean }) { console.log(value, valid); } }
上面示例中,咱們經過 FormBuilder
對象提供的 group()
方法,方便的建立 FormGroup
和 FormControl
對象。接下來咱們來詳細分析一下 FormBuilder
類。ui
// angular2/packages/forms/src/form_builder.ts 片斷 @Injectable() class FormBuilder { // 基於controlsConfig、extra信息,建立FormGroup對象 group(controlsConfig: {[key: string]: any}, extra: {[key: string]: any} = null): FormGroup {} // 基於formState、validator、asyncValidator建立FormControl對象 control( formState: Object, validator: ValidatorFn|ValidatorFn[] = null, asyncValidator: AsyncValidatorFn|AsyncValidatorFn[] = null): FormControl {} //基於controlsConfig、validator、asyncValidator建立FormArray對象 array( controlsConfig: any[], validator: ValidatorFn = null, asyncValidator: AsyncValidatorFn = null): FormArray {} }
首先,咱們先來看一下 group()
方法:this
group(controlsConfig: {[key: string]: any}, extra: {[key: string]: any} = null): FormGroup {}
從 group()
方法簽名中,能夠清楚的知道該方法的輸入參數和返回類型。具體的使用示例以下:
this.user = this.fb.group({ name: ['', [Validators.required, Validators.minLength(2)]], account: this.fb.group({ email: ['', Validators.required], confirm: ['', Validators.required] }) });
接下來咱們來看一下 group()
方法的內部實現:
group(controlsConfig: {[key: string]: any}, extra: {[key: string]: any} = null): FormGroup { // 建立controls對象集合 const controls = this._reduceControls(controlsConfig); // 獲取同步驗證器 const validator: ValidatorFn = extra != null ? extra['validator'] : null; // 獲取異步驗證器 const asyncValidator: AsyncValidatorFn = extra != null ? extra['asyncValidator'] : null; return new FormGroup(controls, validator, asyncValidator); }
咱們在來看一下 _reduceControls()
方法的內部實現:
_reduceControls(controlsConfig: {[k: string]: any}): {[key: string]: AbstractControl} { const controls: {[key: string]: AbstractControl} = {}; // controlsConfig - {name: [...], account: this.fb.group(...)} Object.keys(controlsConfig).forEach(controlName => { // 獲取控件的名稱,而後基於控件對應的配置信息,建立FormControl控件,並保存到controls對象上 controls[controlName] = this._createControl(controlsConfig[controlName]); }); return controls; }
繼續看一下 _createControl()
方法的內部實現:
_createControl(controlConfig: any): AbstractControl { if (controlConfig instanceof FormControl || controlConfig instanceof FormGroup || controlConfig instanceof FormArray) { return controlConfig; } else if (Array.isArray(controlConfig)) { // controlConfig - ['', [Validators.required, Validators.minLength(2)]] const value = controlConfig[0]; // 獲取初始值 // 獲取同步驗證器 const validator: ValidatorFn = controlConfig.length > 1 ? controlConfig[1] : null; // 獲取異步驗證器 const asyncValidator: AsyncValidatorFn = controlConfig.length > 2 ? controlConfig[2] : null; // 建立FormControl控件 return this.control(value, validator, asyncValidator); } else { return this.control(controlConfig); } }
最後咱們看一下 control()
方法的內部實現:
control( formState: Object, validator: ValidatorFn|ValidatorFn[] = null, asyncValidator: AsyncValidatorFn|AsyncValidatorFn[] = null): FormControl { return new FormControl(formState, validator, asyncValidator); }
如今先來總結一下,經過分析 FormBuilder
類的源碼,咱們發現:
this.fb.group({...}, { validator: someCustomValidator })
等價於
new FormGroup({...}, someCustomValidator)
在咱們實現自定義驗證規則前,咱們在來介紹一下 FormGroup
類。
// angular2/packages/forms/src/model.ts 片斷 export class FormGroup extends AbstractControl { constructor( public controls: {[key: string]: AbstractControl}, validator: ValidatorFn = null, asyncValidator: AsyncValidatorFn = null) { super(validator, asyncValidator); this._initObservables(); this._setUpControls(); this.updateValueAndValidity({onlySelf: true, emitEvent: false}); } }
經過源碼咱們發現,FormGroup
類繼承於 AbstractControl
類。在建立 FormGroup
對象時,會把 validator
和 asyncValidator
做爲參數,而後經過 super
關鍵字調用基類 AbstractControl
的構造函數。
接下來咱們來看一下 AbstractControl
類:
// angular2/packages/forms/src/model.ts 片斷 export abstract class AbstractControl { _value: any; ... private _valueChanges: EventEmitter<any>; private _statusChanges: EventEmitter<any>; private _status: string; private _errors: ValidationErrors|null; private _pristine: boolean = true; private _touched: boolean = false; constructor(public validator: ValidatorFn, public asyncValidator: AsyncValidatorFn) {} // 獲取控件的valid狀態,用於表示控件是否經過驗證 get valid(): boolean { return this._status === VALID; } // 獲取控件的invalid狀態,用於表示控件是否經過驗證 get invalid(): boolean { return this._status === INVALID; } // 獲取控件的pristine狀態,用於表示控件值未改變 get pristine(): boolean { return this._pristine; } // 獲取控件的dirty狀態,用於表示控件值已改變 get dirty(): boolean { return !this.pristine; } // 獲取控件的touched狀態,用於表示控件已被訪問過 get touched(): boolean { return this._touched; } ... }
使用 AbstractControl 不是實現咱們自定義 FormGroup 驗證的關鍵,由於咱們也能夠注入 FormGroup
來實現與表單控件進行交互。如今咱們再來觀察一下最初的代碼:
@Component({...}) export class SignupFormComponent implements OnInit { user: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() { this.user = this.fb.group({ name: ['', [Validators.required, Validators.minLength(2)]], account: this.fb.group({ email: ['', Validators.required], confirm: ['', Validators.required] }) }); } }
接下來咱們要實現的自定義驗證規則是,確保 email
字段的值與 confirm
字段的值可以徹底一致。咱們能夠經過 AbstractControl
來實現該功能,首先咱們先來定義驗證函數:
email-matcher.ts
export const emailMatcher = () => {};
下一步,咱們須要注入 AbstractControl
:
export const emailMatcher = (control: AbstractControl): {[key: string]: boolean} => { };
在 Angular 4.x Reactive Forms 文章中,咱們介紹了經過 FormGroup
對象 (FormGroup 類繼承於AbstractControl),提供的 get()
方法,能夠獲取指定的表單控件。get() 方法的簽名以下:
get(path: Array<string|number>|string): AbstractControl { return _find(this, path, '.'); } // 使用示例 - 獲取sub-group的表單控件 this.form.get('person.name'); -OR- this.form.get(['person', 'name']);
具體示例以下:
<div class="error" *ngIf="user.get('foo').touched && user.get('foo').hasError('required')"> This field is required </div>
瞭解完 AbstractControl,接下來咱們來更新一下 emailMatcher
函數:
export const emailMatcher = (control: AbstractControl): {[key: string]: boolean} => { const email = control.get('email'); const confirm = control.get('confirm'); };
上面的示例中,control 表示的是 FormGroup
對象,email
和 confirm
都是表示 FormControl
對象。咱們能夠在控制檯中輸出它們的值:
► FormGroup {asyncValidator: null, _pristine: true, _touched: false, _onDisabledChange: Array[0], controls: Object…} ► FormControl {asyncValidator: null, _pristine: true, _touched: false, _onDisabledChange: Array[1], _onChange: Array[1]…} ► FormControl {asyncValidator: null, _pristine: true, _touched: false, _onDisabledChange: Array[1], _onChange: Array[1]…}
實際上 emailMatcher
自定義驗證規則,就是比較 email
與 confirm
控件的值是否一致。若是它們的值是一致的,那麼返回 null,表示驗證經過,沒有出現錯誤。具體代碼以下:
export const emailMatcher = (control: AbstractControl): {[key: string]: boolean} => { const email = control.get('email'); const confirm = control.get('confirm'); if (!email || !confirm) return null; if (email.value === confirm.value) { return null; } };
上述代碼意味着若是一切正常,咱們都不會返回任何錯誤。如今咱們須要添加自定義驗證。
咱們先來看一下,在 HTML 模板中,咱們自定義驗證規則的預期使用方式:
... <div formGroupName="account"> <label> <span>Email address</span> <input type="email" placeholder="Your email address" formControlName="email"> </label> <label> <span>Confirm address</span> <input type="email" placeholder="Confirm your email address" formControlName="confirm"> </label> <div class="error" *ngIf="user.get('account').touched && user.get('account').hasError('nomatch')"> Email addresses must match </div> </div> ...
忽略掉其它無關的部分,咱們只關心如下的代碼片斷:
user.get('account').hasError('nomatch')
這意味着,咱們須要先獲取 account 對象 (FormGroup實例),而後經過 hasError()
方法,判斷是否存在 nomatch
的錯誤。接下來咱們按照該需求更新 emailMatcher
函數,具體以下:
export const emailMatcher = (control: AbstractControl): {[key: string]: boolean} => { const email = control.get('email'); const confirm = control.get('confirm'); if (!email || !confirm) return null; return email.value === confirm.value ? null : { nomatch: true }; };
最後,咱們須要導入咱們的自定義驗證規則,而後在調用 fb.group()
建立 account FormGroup對象時,設置第二個參數,具體示例以下:
... import { emailMatcher } from './email-matcher'; ... ngOnInit() { this.user = this.fb.group({ name: ['', Validators.required], account: this.fb.group({ email: ['', Validators.required], confirm: ['', Validators.required] }, { validator: emailMatcher }) }); } ...
完整的示例代碼以下:
signup.interface.ts
export interface User { name: string; account: { email: string; confirm: string; } }
email-matcher.ts
export const emailMatcher = (control: AbstractControl): {[key: string]: boolean} => { const email = control.get('email'); const confirm = control.get('confirm'); if (!email || !confirm) { return null; } return email.value === confirm.value ? null : { nomatch: true }; };
signup-form.component.ts
import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { emailMatcher } from './email-matcher'; @Component({ selector: 'signup-form', template: ` <form class="form" novalidate (ngSubmit)="onSubmit(user)" [formGroup]="user"> <label> <span>Full name</span> <input type="text" placeholder="Your full name" formControlName="name"> </label> <div class="error" *ngIf="user.get('name').touched && user.get('name').hasError('required')"> Name is required </div> <div formGroupName="account"> <label> <span>Email address</span> <input type="email" placeholder="Your email address" formControlName="email"> </label> <label> <span>Confirm address</span> <input type="email" placeholder="Confirm your email address" formControlName="confirm"> </label> <div class="error" *ngIf="user.get('account').touched && user.get('account').hasError('nomatch')"> Email addresses must match </div> </div> <button type="submit" [disabled]="user.invalid">Sign up</button> </form> ` }) export class SignupFormComponent implements OnInit { user: FormBuilder; constructor(public fb: FormBuilder) {} ngOnInit() { this.user = this.fb.group({ name: ['', Validators.required], account: this.fb.group({ email: ['', Validators.required], confirm: ['', Validators.required] }, { validator: emailMatcher }) }); } onSubmit({ value, valid }) { console.log(value, valid); } }
具體詳情,能夠查看線上示例。