模型驅動表單
之前有篇博文總結了 模版驅動表單 , 以及 模版驅動表單的自定義校驗 , 本篇總結下模型驅動表單。
與模版驅動表單是不同的編程思路,偏向於數據模型。先在組件中建立表單控件的對象樹,再綁定到組件模版的原生表單控件上。而模版驅動表單則是在組件模版中使用了內置的 ngForm、ngModel指令,這些指令會自動完成很多工作,以達到雙向綁定、監聽form和表單控件的狀態等等 的目的。雖然模版驅動表單寫起來更見的簡潔方便,因為指令自動完成了很多工作,但是也正式由於委托指令,所以會導致異步的問題。官網描述 如下:
響應式表單是同步的而模板驅動表單是異步的。
使用響應式表單,你會在代碼中創建整個表單控件樹。 你可以立即更新一個值或者深入到表單中的任意節點,因為所有的控件都始終是可用的。
模板驅動表單會委托指令來創建它們的表單控件。 為了消除“檢查完后又變化了”的錯誤,這些指令需要消耗一個以上的變更檢測周期來構建整個控件樹。 這意味着在從組件類中操縱任何控件之前,你都必須先等待一個節拍。
雖然目前本人目前使用模版驅動表單,還沒有遇到因異步導致的問題,但是也許在某一天,bug會從天而降。
模型驅動表單需要引入模塊 : ReactiveFormsModule 。而模版驅動表單需要引入 FormsModule。
Angular提供了一些方法來構建表單控件的對象樹。FormControl,FormGroup,FormArray是構建表單模型的三種表單類,它們有共同的基類 AbstractControl。這三種表單類有不同的作用。
FormControl
FormControl用來構建一個單獨的表單控件的值和狀態,它會對應這模型中的一個表單元素。FormControl類的構造函數如下:
constructor(formState?: any, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null);
接受三個可選參數:初始值、驗證器、異步驗證器。簡單的創建一個formControl
//引入依賴 import { FormControl , Validators} from '@angular/forms'; //創建對象 設置初始值,和校驗規則 public name = new FormControl('' , Validators.required);
使用 formControl 可將此控件對象綁定到組件模板中
<input class="form-control" type="text" id="login-name" placeholder="請輸入登錄帳號" [formControl]="name">
隨便輸入內容,已經能正確監聽控件的值和狀態了!每當輸入框內容中有變化,name(控件對象)都會隨之改變。

此時都是輸入內容變化都會觸發控件對象更新,那么如何修改觸發更新的時機呢?在FormControl的構造函數中有 AbstractControlOptions ,其中的 updateOn 配置項可以修改觸發時機。從源碼中可以查看到有三種不同的方式, 值變化,失焦,提交。

// 失焦時觸發更新 public name = new FormControl('' , { validators : Validators.required, updateOn : 'blur' });
FormGroup
FormGroup對象用來跟蹤一組 AbstractControl 的值和狀態,即可以跟蹤多個formControl和FormGroup。構造函數如下:
constructor(controls: { [key: string]: AbstractControl; }, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null);
創建一個簡單的formGroup,並綁定到模版中
// 簡單的formGroup public loginForm = new FormGroup({ name : new FormControl('', Validators.maxLength(20)), psw : new FormControl('') }, Validators.required); // 綁定到模版中 <form class="login-area" (submit)="login()" [formGroup] = "loginForm"> <div class="form-group"> <input class="form-control" type="text" id="login-name" placeholder="請輸入登錄帳號" formControlName = 'name'> </div> <div class="form-group"> <input class="form-control" type="password" id="login-pwd" placeholder="請輸入登錄密碼" formControlName = 'psw'> </div> <div class="form-group"> {{loginForm.value | json}} </div> </form>
這里就不能再使用[formControl]來綁定表單控件了,因為這里創建的是FormGroup,已經通過[formGroup]綁定到了 form上了,這你需要通過FormGroup來找到FormControl,在表單控件中用formControlName來指定對應的FormControl對象。
FormBuilder
以上的方式構建表單對象樹太過於繁瑣,需要使用很多的new FormGroup() (構建多級表單的時候) 和 new FromControl() ,FormBuilder就是簡化構造方式的的服務。FormBuilder服務中有三個工廠函數 group() control() array() 可以簡化代碼量。使用FormBuilder的方式再次構建上面的表單對象樹。
public loginForm: FormGroup;
constructor(
private fb: FormBuilder
) {
已經減少了一些代碼量,在表單很龐大的時候,對比會特別明顯。注意group工具函數指定表單組的校驗規則時與原new GroupForm()參數放置的位置不一樣。
/** * Construct a new {@link FormGroup} with the given map of configuration. * Valid keys for the `extra` parameter map are `validator` and `asyncValidator`. * * See the {@link FormGroup} constructor for more details. */ group(controlsConfig: { [key: string]: any; }, extra?: { [key: string]: any; } | null): FormGroup;
多級FormGroup
為了更高效的管理表單,可以把類似的表單放在一起,形成一個FormGroup,這樣就可以同時管理這些類似表單的值和狀態。這樣創建就會產生多級FormGroup。再通過formGroupName把子group導向模版中。
this.loginForm = fb.group({ name: this.fb.group({ firstname: ['' , Validators.required], lastname: '' }), psw: ['' , Validators.required] }); <div formGroupName="name"> <div class="form-group"> <input class="form-control" type="text" id="login-name" placeholder="請輸入姓" formControlName = 'firstname'> </div> <div class="form-group"> <input class="form-control" type="text" id="login-name" placeholder="請輸入名" formControlName = 'lastname'> </div> </div>
獲取控件狀態
在FormGroup中可以通過get()方法定位到表單控件對象,然后就能夠獲取到各種狀態了,和之前模版驅動表單中介紹的一樣。可以簡單的封裝一個獲取函數,這樣在模版中就能非常方便的獲取到各個formControl。
getFormControl(name: string) { return this.loginForm.get(name); } <div class="form-group"> {{getFormControl('name').status}} </div> <div class="form-group"> {{getFormControl('name.firstname').status}} </div>
自定義校驗
模型驅動表單的自定義校驗方便很多,因為可以直接自定義各級formControl。以上面的多級group為例,添加定義的校驗規則:firstname和lastname不能輸入相同的值。
sameName (): ValidatorFn { return (control: AbstractControl): { [key: string]: any} => { // control指向使用它的formControl或者formGroup return control.get('firstname').value === control.get('lastname').value ? { 'sameName' : true } : null; // 子formcontrol的值相同就返回錯誤 }; }
在模型中使用此校驗規則
this.loginForm = fb.group({ name: this.fb.group({ firstname: ['' , Validators.required], lastname: [''] } , { validator : [ // 對整個name的校驗規則 this.sameName(), Validators.minLength(2) ] }), psw: ['' , Validators.required] });
