angular 更新表單值的兩種方法: setvalue,patchvalue


使用 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);
}
忽略所有的函數參數和類型,它所做的就是調用 setValue() 方法,設置控件的值。另外使用 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'}
跟 patchValue() 方法一樣,我們內部也是包含一個 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 數據結構一致才能進行更新。

 
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM