關於Angular響應式表單的三種更新值的討論

Angular響應式表單相比較模板驅動表單更大操做性更易測試性。所以,我更推薦這類表單創造方式。typescript

當一個用於修改用戶信息的表單,數據的來源老是來自遠程;而對於一個 FormGroup 的建立總在 ngOnInit 中完成。所以,這裏會有一個表單更新值的問題。app

而一般咱們會透過 FormGroup 下的三種方式 setValuepatchValuereset 將值寫入表單當中。測試

固然,或許我說的這三種方式時你壓根就沒有作過,那說明在表單上你依賴的是雙向綁定 [(ngModel)],這自己就不是符合 Angular 響應式表單的牛B之處了。所以,在此咱們不討論這種這種方式。ui

1、建立響應式表單

咱們模擬一個用戶信息修改的表單所須要的字段,可能包括:emailnickname 等。this

若是以API的方式與現實字段之間產生一個關聯,那麼 FormGroup 表示一個表單,FormControl 表示表單中的字段。所以,FormControl 必須包裹在 FromGroup 下面。雙向綁定

下面,咱們先簡單的構建一個響應式表單。code

別忘記導入 ReactiveFormsModule 模塊。orm

@Component({
    selector: 'app-validation',
    template: `
    <form [formGroup]="form" (ngSubmit)="_submitForm(form)">
        <input type="email" formControlName="email">
        <input type="text" formControlName="nickname">
        <button type="submit" [disabled]="form.invalid">Submit</button>
    </form>
    `
})
export class UserEditComponent {
    constructor(private fb: FormBuilder, private route: ActivatedRoute) {}
    
    ngOnInit() {
        this.form = this.fb.group({
            email: ['', Validators.compose([Validators.required, Validators.email])],
            nickname: ['', [Validators.required]]
        });
        
        this.route.params
            .switchMap((params: Params) => loadUser(+params['id']))
            .subscribe(data => {
                // Updating value
            });
    }
    
    loadUser() {
        return Observable.of({ email: 'xx@xx.com', nickname: 'cipchk' }).delay(1000);
    }
    
    _submitForm({ value }) {
        // Save value
    }
}

以上的這些代碼再熟悉不過了。假設 UserEditComponent 是由路由 /user/edit/1 觸發,那麼會發生幾個幾件事情。對象

首先,建立一個空的響應式表單 formip

this.form = this.fb.group({
    email: ['', Validators.compose([Validators.required, Validators.email])],
    nickname: ['', [Validators.required]]
});

表單的內容有 emailnickname 兩個字段。

  • email 必填項且必須是標準 Email 格式。
  • nickname 必填項。

然而,HTML中,除了 formGroupformControlName 的配置之外,也看不到任何有關對錶單的校驗代碼。但,當咱們輸入一個無效 Email 時 input 會自動加上 ng-invalid 類。

這即是響應式表單的魅力。

如今咱們回到正題,將分別針對 setValuepatchValuereset 三種不一樣更新表單值實際上會發生什麼。

2、patchValue

正如名稱那般,打補丁。假如咱們在 email 文本框裏輸入:xx@xx.com,接着調用:

this.form.patchValue({ nickname: 'cipchk' });

最終的結果是兩個字段同時擁有值,由於這裏咱們只對 nickname 設置了值,而 email 並無,那只是先前人爲錄入的數據。

那麼 patchValue 實際上作了什麼呢?

patchValue(value: {[key: string]: any}, options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
    Object.keys(value).forEach(name => {
      if (this.controls[name]) {
        this.controls[name].patchValue(value[name], {onlySelf: true, emitEvent: options.emitEvent});
      }
    });
    this.updateValueAndValidity(options);
}

首先,利用 Object.keys 查找主鍵,並以主鍵名查找相應的 FromControl 實例對象:

Object.keys({ nickname: 'cipchk' }).forEach(name => { 
    console.log(name); 
});
// [ 'nickname' ]

而後,更新值:

this.controls[name].patchValue(value[name], {onlySelf: true, emitEvent: options.emitEvent});

FromControl 實例的 patchValueFromGroup 不一樣,他只是單純的更新 FromControle 實例對象中的 value 值。

value 至關於表單實際值,還記得先前HTML中的 formControlName 就是將實例與DOM產生聯繫,這也就是爲何不須要在DOM中使用雙向綁定的緣由。

3、setValue

patchValue 有一點不同,當咱們提供一個 FromGroup 中並不存在的字段時,會拋出一個錯誤。除此以外,與 patchValue 並沒有不一樣。

setValue(value: {[key: string]: any}, options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
    this._checkAllValuesPresent(value);
    Object.keys(value).forEach(name => {
      this._throwIfControlMissing(name);
      this.controls[name].setValue(value[name], {onlySelf: true, emitEvent: options.emitEvent});
    });
    this.updateValueAndValidity(options);
}

主要是 this._throwIfControlMissing(name); 當傳遞的對象有一個不是 FromControl 時直接拋棄一個 Error

_throwIfControlMissing(name: string): void {
    if (!Object.keys(this.controls).length) {
      throw new Error(`
        There are no form controls registered with this group yet.  If you're using ngModel,
        you may want to check next tick (e.g. use setTimeout).
      `);
    }
    if (!this.controls[name]) {
      throw new Error(`Cannot find form control with name: ${name}.`);
    }
}

4、reset

正常狀況下,表單須要提供一個重置按鈕時調用此方法。

reset(formState: any = null, options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
    this._applyFormState(formState);
    this.markAsPristine(options);
    this.markAsUntouched(options);
    this.setValue(this._value, options);
}

除了恢復校驗狀態之外。最後一句代碼是調用 setValue,這等同上一節說的。所以,當咱們調用此方法時,容許咱們直接傳遞一個數據對象作爲重置後的默認值,好比:

<button (click)="form.reset({ nickname: 'xx' })">Reset</button>

重置表單後並設置 nickname 默認值爲:xx。

結論

每一種不一樣更新值方式都會有不同的結果,當咱們回頭過看開頭中留下來的:

// Updating value

若是是你,你會怎麼寫呢?

Happy coding!

相關文章
相關標籤/搜索