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 的值要特別小心,務必確認同步與非同步的發生順序