Angular Reacitve Form 內有一套值的更新流程,從 FormGroup 到 FormControl 間到底是如何進行資料的更新,有什麼要留意的地方,在這篇筆記內我盡量整理 (會持續潤稿)
首先先將重點放在 FormGroup 和 FormControl 這兩個主體上就好,我們都知道 FormGroup 和 FormControl 都是繼承 AbstractControl,不同的部分就在各 class 上重新撰寫 ,好加在的是這篇文章要看的程是碼都在同一個檔案上
而此篇要探討的是 setValue、patchValue 和 valuechanges 這三件事情,到底更新的流程是什麼,為什麼這件事情很重要,當在寫連動表單或是物件時,流程一但搞錯就會讓你除錯除到死
原始碼解析
先從最底層的元件來看,對於 FormControl 來說,patchValue 與 setValue 是沒有差異的,即使呼叫 patchValue,還是會去執行 setValue
1 2 3 4 5 6 7 8
| patchValue(value: any, options: { onlySelf?: boolean, emitEvent?: boolean, emitModelToViewChange?: boolean, emitViewToModelChange?: boolean } = {}): void { this.setValue(value, options); }
|
而 setValue 會執行的程是碼其實也不多,所有的工作都落在 updateValueAndValidity 上
1 2 3 4 5 6 7 8 9 10 11 12 13
| setValue(value: any, options: { onlySelf?: boolean, emitEvent?: boolean, emitModelToViewChange?: boolean, emitViewToModelChange?: boolean } = {}): void { (this as {value: any}).value = this._pendingValue = value; if (this._onChange.length && options.emitModelToViewChange !== false) { this._onChange.forEach( (changeFn) => changeFn(this.value, options.emitViewToModelChange !== false)); } this.updateValueAndValidity(options); }
|
updateValueAndValidity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| updateValueAndValidity(opts: {onlySelf?: boolean, emitEvent?: boolean} = {}): void { this._setInitialStatus(); this._updateValue();
if (this.enabled) { this._cancelExistingSubscription(); (this as {errors: ValidationErrors | null}).errors = this._runValidator(); (this as {status: string}).status = this._calculateStatus();
if (this.status === VALID || this.status === PENDING) { this._runAsyncValidator(opts.emitEvent); } }
if (opts.emitEvent !== false) { (this.valueChanges as EventEmitter<any>).emit(this.value); (this.statusChanges as EventEmitter<string>).emit(this.status); }
if (this._parent && !opts.onlySelf) { this._parent.updateValueAndValidity(opts); } }
|
主要程式碼是寫在 AbstractControl 裡
-
line 2: 取得目前物件的狀態,如果是 disabled 的話,則 status 將會是 DISABLED 不然就是 VALID
-
line 3: 只有在 FormGroup 和 FormArray 有實做,根據目前 Group / Array 內子物件的值更新本身的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| _updateValue(): void { (this as {value: any}).value = this._reduceValue(); }
_reduceValue() { return this._reduceChildren( {}, (acc: {[k: string]: AbstractControl}, control: AbstractControl, name: string) => { if (control.enabled || this.disabled) { acc[name] = control.value; } return acc; }); }
|
-
line 5 ~ 13: 如果物件狀態是 enabled 才會進行驗證動作,驗證的順序為
- 同步驗證先執行,並更新狀態
- 如果狀態是
VALID 或是 PENDING 才會進行非同步驗證
-
根據傳入參數 emitEvent 來決定是否觸發 valueChanges 和 statusChanges
-
根據 onlySelf 來決定是否要觸發父層的 updateValueAndValidity
這裡有一個小技巧,所傳入的參數值並沒有設定預設值,而是很明確指定判斷值,這招可以學一下
1 2 3
| if (opts.emitEvent !== false) { ... }
|
patchValue
1 2 3 4 5 6 7 8 9
| 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); }
|
- 會先更新子物件,並設定只會更新子物件本身
- 在跑自己的
updateValueAndValidity
細節
一般使用基本上不會遇到什麼問題,但如果遇到連動的情況,就要特別小心
1 2 3 4
| formData = new FormGroup({ firstName: new FormControl(), lastName: new FormControl() });
|
情境 1
請各位想想根據第一段的原始碼解析,這邊跑出來結果會是什麼呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| this.formData.valueChanges.subscribe({ next: value => console.log("formGroup", this.formData.value) });
this.formData.controls.firstName.valueChanges.subscribe({ next: value => console.log("firstName:", value, "formGroup value:" ,this.formData.value) });
this.formData.controls.lastName.valueChanges.subscribe({ next: value => console.log("lastName:", value, "formGroup value:" ,this.formData.value) });
this.formData.patchValue({ firstName: "1", lastName: "2" });
|
結果如下

為什麼呢? 來模擬一下執行的步驟
formGroup.patchValue : 會根據傳進去的資料依序更新 firstname 與 lastName formControl
firstName FormControl 更新自身的值,但因為 FormGroup 傳入 onlySelf 為 true,所以不會更新 parent 的值
lastName FormControl 更新自身的值,但因為 FormGroup 傳入 onlySelf 為 true,所以不會更新 parent 的值
FormGroup 執行 updateValueAndValidity
- 根據
children 更新自身的值
- 送出
valueChanges event
- 結束
情境 2
這邊跑出來結果會是什麼呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| this.formData.valueChanges.subscribe({ next: value => console.log("formGroup", this.formData.value) });
this.formData.controls.firstName.valueChanges .pipe(tap(() => this.formData.controls.lastName.setValue("3"))) .subscribe({ next: value => console.log( "firstName:", value, "formGroup value:", this.formData.value ) });
this.formData.controls.lastName.valueChanges.subscribe({ next: value => console.log("lastName:", value, "formGroup value:", this.formData.value) });
this.formData.patchValue({ firstName: "1", lastName: "2" });
|
結果如下

你想對了嗎? 我們來模擬一下執行的步驟
formGroup.patchValue : 會根據傳進去的資料依序更新 firstname 與 lastName formControl
firstName FormControl 更新自身的值,但因為 FormGroup 傳入 onlySelf 為 true ,所以不會更新 parent 的值
- 過程中去執行更新
lastName FormControl 的值 ,參數接為預設值,所以 emitEvent: true,onlySelf:false
- 觸發
lastName FormControl 的 valueChanges
- 觸發父層的
updateValueAndValidity
firstName FormControl 自身 valueChanges 流程跑玩
lastName FormControl 更新自身的值,但因為 FormGroup 傳入 onlySelf 為 true,所以不會更新父層的值
FormGroup 執行 updateValueAndValidity
- 根據
children 更新自身的值
- 送出
valueChanges event
- 結束
情境 3
這邊跑出來結果會是什麼呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| this.formData.valueChanges.subscribe({ next: value => console.log("formGroup", this.formData.value) });
this.formData.controls.firstName.valueChanges .pipe(tap(() => this.formData.controls.lastName.setValue("3", {emitEvent: false}))) .subscribe({ next: value => console.log( "firstName:", value, "formGroup value:", this.formData.value ) });
this.formData.controls.lastName.valueChanges.subscribe({ next: value => console.log("lastName:", value, "formGroup value:", this.formData.value) });
this.formData.patchValue({ firstName: "1", lastName: "2" });
|
結果如下,你想對了嗎?

情境 4
這邊跑出來結果會是什麼呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| this.formData.valueChanges.subscribe({ next: value => console.log("formGroup", this.formData.value) });
this.formData.controls.firstName.valueChanges .pipe(tap(() => this.formData.controls.lastName.setValue("3", {emitEvent: false, onlySelf: true}))) .subscribe({ next: value => console.log( "firstName:", value, "formGroup value:", this.formData.value ) });
this.formData.controls.lastName.valueChanges.subscribe({ next: value => console.log("lastName:", value, "formGroup value:", this.formData.value) });
this.formData.patchValue({ firstName: "1", lastName: "2" });
|
結果如下,你想對了嗎?

情境 5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| this.formData.valueChanges.subscribe({ next: value => console.log("formGroup", this.formData.value) });
this.formData.controls.firstName.valueChanges .pipe( mergeMap(() => timer(1000, 0).pipe( take(1), tap(() => this.formData.controls.lastName.setValue("3", { emitEvent: false, onlySelf: true }))) ) ) .subscribe({ next: value => console.log( "firstName:", value, "formGroup value:", this.formData.value ) });
this.formData.controls.lastName.valueChanges.subscribe({ next: value => console.log("lastName:", value, "formGroup value:", this.formData.value) });
this.formData.patchValue({ firstName: "1", lastName: "2" });
|
結果如下,你想對了嗎?

心得
Reactive Form 將 valueChanges 包成 Observable 是很方便,要做一些連動的動作可以如流水般的操作,但問題是,如果資料流的線路沒搞對,就會發生為什麼這裡取的資料是錯的問題發生。這些細節的部分都是文件中沒有提到的
所以,在 FormControl 的 valuechanges 流中操作其它 FormControl 的值要特別小心,務必確認同步與非同步的發生順序