使用 patchValue() 方法會比使用 setValue() 方法更好!
1、patchValue()
// angular2/packages/forms/src/model.ts export class FormGroup extends AbstractControl { ... patchValue( value: {[key: string]: any},{onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}): void { Object.keys(value).forEach(name => { if (this.controls[name]) { this.controls[name].patchValue(value[name], {onlySelf: true, emitEvent}); } }); this.updateValueAndValidity({onlySelf, emitEvent}); } } // 使用示例 const form = new FormGroup({ first: new FormControl(), last: new FormControl() }); console.log(form.value); // {first: null, last: null} form.patchValue({first: 'Nancy'}); console.log(form.value); // {first: 'Nancy', last: null}
從源碼中我們可以看出,patchValue() 方法會獲取輸入參數對象的所有 key 值,然后循環調用內部控件的 patchValue()
方法,具體代碼如下:
Object.keys(value).forEach(name => { if (this.controls[name]) { this.controls[name].patchValue(value[name], {onlySelf: true, emitEvent}); } });
首先,Object.keys()
會返回對象 key 值的數組,例如:
const man = {name : 'Semlinker', age: 30}; Object.keys(man); // ['name', 'age']此外this.controls
包含了 FormGroup 對象中的所有 FormControl 控件,我們可以通過this.controls[name]
方式,訪問到 name 對應的控件對象。
現在讓我們來回顧一下創建 FormGroup 對象的相關代碼
this.form = this.fb.group({ name: ['', Validators.required], event: this.fb.group({ title: ['', Validators.required], location: ['', Validators.required] }) });
與之相對應的對象模型如下:
{ name: '', event: { title: '', location: '' } }
因此要更新該模型的值,我們可以利用 FormGroup
對象的 patchValue()
方法:
this.form.patchValue({ name: 'Semlinker', event: { title: 'Angular 4.x\'s Road', location: 'Xiamen' } });
以上代碼將會通過循環的方式,更新每個 FormControl
控件。接下來我們看一下 FormControl
中 patchValue() 方法的具體實現:
patchValue(value: any, options: { onlySelf?: boolean, emitEvent?: boolean, emitModelToViewChange?: boolean, emitViewToModelChange?: boolean } = {}): void { this.setValue(value, options); }
patchValue()
方法有什么好處呢?假設我們使用
firebase
,那么當我們從 API 接口獲取數據對象時,對象內部可能會包含
$exists
和
$key
屬性。而當我們直接使用返回的數據對象作為參數,直接調用 patchValue() 方法時,不會拋出任何異常:
this.form.patchValue({ $exists: function () {}, $key: '-KWihhw-f1kw-ULPG1ei', name: 'Semlinker', event: { title: 'Angular 4.x\'s Road', location: 'Xiamen' } });
其實沒有拋出異常的原因,是因為在 patchValue() 內部循環時,我們有使用 if
語句進行條件判斷。
2、setValue
FormGroup 類中的 setValue() 方法的具體實現:
setValue( value: {[key: string]: any}, {onlySelf, emitEvent}: {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}); }); this.updateValueAndValidity({onlySelf, emitEvent}); } // 使用示例 const form = new FormGroup({ first: new FormControl(), last: new FormControl() }); console.log(form.value); // {first: null, last: null} form.setValue({first: 'Nancy', last: 'Drew'}); console.log(form.value); // {first: 'Nancy', last: 'Drew'}
Object.keys()
的循環,但在循環開始之前,我們會先調用
_checkAllValuesPresent()
方法,對輸入值進行校驗。 另外
_checkAllValuesPresent()
方法的具體實現如下:
_checkAllValuesPresent(value: any): void { this._forEachChild((control: AbstractControl, name: string) => { if (value[name] === undefined) { throw new Error(`Must supply a value for form control with name: '${name}'.`); } }); }
該方法內部通過 _forEachChild()
遍歷內部的 FormControl 控件,來確保我們在調用 setValue()
方法時,設置的參數對象中,會包含所有控件的配置信息。如果 name
對應的配置信息不存在,則會拋出異常。
在 _checkAllValuesPresent()
驗證通過后,Angular 會進入 Object.keys()
循環,此外在調用 setValue()
方法前,還會優先調用 _throwIfControlMissing()
判斷控件是否存在,該方法的實現如下:
_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}.`); } }
this.controls
是否存在,如果存在進一步判斷
name
對應的
FormControl
控件是否存在。當
_throwIfControlMissing()
驗證通過后,才會最終調用
FormControl
控件的 setValue() 方法:
this.controls[name].setValue(value[name], {onlySelf: true, emitEvent})
我們來看一下 FormControl
類中,setValue() 方法的具體實現:
setValue(value: any, {onlySelf, emitEvent, emitModelToViewChange, emitViewToModelChange}: { onlySelf?: boolean, emitEvent?: boolean, emitModelToViewChange?: boolean, emitViewToModelChange?: boolean } = {}): void { this._value = value; if (this._onChange.length && emitModelToViewChange !== false) { this._onChange.forEach((changeFn) => changeFn(this._value, emitViewToModelChange !== false)); } this.updateValueAndValidity({onlySelf, emitEvent}); }
該方法的第一個參數,就是我們要設置的值,第二個參數是一個對象:
- onlySelf:若該值為 true,當控件的值發生變化后,只會影響當前控件的驗證狀態,而不會影響到它的父組件。默認值是 false。
- emitEvent:若該值為 true,當控件的值發生變化后,將會觸發
valueChanges
事件。默認值是 true - emitModelToViewChange:若該值為 true,當控件的值發生變化時,將會把新值通過
onChange
事件通知視圖層。若未指定emitModelToViewChange
的值,這是默認的行為。 - emitViewToModelChange:若該值為 true,
ngModelChange
事件將會被觸發,用於更新模型。若未指定emitViewToModelChange
的值,這是默認的行為。
其實僅僅通過上面的代碼,我們還是沒完全搞清楚 setValue()
方法內部真正執行流程。如我們不知道如何注冊 changeFn 函數和 updateValueAndValidity()
方法的內部處理邏輯,接下來我們先來看一下如何注冊 changeFn 函數
export class FormControl extends AbstractControl { /** @internal */ _onChange: Function[] = []; ... /** * Register a listener for change events. */ registerOnChange(fn: Function): void { this._onChange.push(fn); } }
現在我們來回顧一下 setValue() 的相關知識點。對於 FormGroup
對象,我們可以通過 setValue()
方法更新表單的值,具體使用示例如下:
this.form.setValue({ name: 'Semlinker', event: { title: 'Angular 4.x\'s Road', location: 'Xiamen' } });
以上代碼成功運行后,我們就能成功更新表單的值。但如果我們使用下面的方式,就會拋出異常:
this.form.setValue({ $exists: function () {}, $key: '-KWihhw-f1kw-ULPG1ei', name: 'Semlinker', event: { title: 'Angular 4.x\'s Road', location: 'Xiamen' } });
總結:patchValue 可以只更新副本的數據,而setValue則必須與form 數據結構一致才能進行更新。