Web Angular - Reactive Form

前言

在建立表單的部份,Angular 與 AngularJS 最大的不同就是多了 Reactive Form 的方式。由於 Reactive Form 比起一般 Template-Driven Form 更具彈性且好掌控,所以就來大致介紹一下與 Reactive Form 相關的流程機制。

FormControl

FormControl 是 reactive form 中最基本且必要的角色。它是一個完全獨立的 Instance,裡面包含了 value / validators / status 等重要 member variables。想像一下,如果我們需要一個輸入暱稱的 input 欄位,而 formControl 的任務就是要儲存使用者所輸入的暱稱,以及操作內部的 Validators 對輸入的暱稱字元進行驗證。

FormControl 運作流程

下圖簡單地介紹 FormControl 的內部機制,首先在建立 FormControl Instance 時,我們可以透過 constructor 初始化 value 和設置想要的 validators function。舉例來說,我們提供給使用者修改暱稱的欄位,需要在欄位上先填好使用者原本的暱稱,以及設置好暱稱驗證方式。

![Angular FormControl]({{ site.url }}/assets/images/angular-formControl.png)

而當有新的 value 進來時,會透過 setValue() 這個 function 來更新 value 參數。在 setValue() 更新完 value 之後,就會進行值驗證,看看新進來的 value 是否符合規範,,並將 status 狀態更新為 invalid 或是 valid。

1const control = new FormControl('', Validators.required);
2control.setValue('newValue');

由於有了值的驗證機制,所以開發者除了可以使用 setValue() 來改變 value variable 之外,還可以透過 FormControl 中的 status 來得知值的驗證結果。這在顯示欄位輸入提示上非常有用。

 1// Formcontrol source code
 2setValue(value: any, options: {
 3  onlySelf?: boolean,
 4  emitEvent?: boolean,
 5  emitModelToViewChange?: boolean,
 6  emitViewToModelChange?: boolean
 7  } = {}): void {
 8    // update value
 9    (this as{value: any}).value = this._pendingValue = value;
10    
11    // 中間省略
12
13    // update status function
14    this.updateValueAndValidity(options);
15}

formControl Directive

上面有提到, FormControl 本身是一個獨立物件,那該如何讓 FormControl 與 DOM 產生連結? formControl Directive 所扮演的角色就是 FormControl Instance 和 DOM 之間的橋樑,讓使用者所輸入的值可以被更新在 FormControl 的 value 參數上,或是當 FormControl 的 value 改變時,新的值可以被寫上 DOM element 。

我們可以在官網看到以下例子:

1@Component({
2  ///...
3})
4export class ExampleComponent {
5  control = new FormControl('', Validators.required);
6}
1<input type="text" [formControl]="control">
2<p>{{control.value}}</p>

這就是將新建立好的 ForControl Instance 傳遞給 formControl Directive 來做操作的意思。

藉由 formControl Directive 的協助,ForControl Instance 就可以獲得使用者所輸入的值,然後我們就能把值顯示在畫面上。 (如同上方的 {{control.value}} )

 1// formControl Directive source code
 2@Directive({
 3  selector: '[formControl]', 
 4  providers: [formControlBinding], 
 5  exportAs: 'ngForm'})
 6export class FormControlDirective extends NgControl implements OnChanges {
 7  //...
 8  ngOnChanges(changes: SimpleChanges): void {
 9    //...
10    setUpControl(this.form, this);
11    //...
12  }
13}
14
15// setUpControl
16function setUpControl(control: FormControl, dir: NgControl): void {
17  // write a value to target DOM element. 
18  dir.valueAccessor !.writeValue(control.value);
19  setUpViewChangePipeline(control, dir);
20  setUpModelChangePipeline(control, dir);
21  //...
22}

ValueAccessor

下方圖顯示 formControl Directive 如何連結 FormControl Instance 和 DOM element。

![Angular formControl directive]({{ site.url }}/assets/images/angular-directive.png)

可以看到 DOM element 和 formControl Directive 之間有個 ValueAccessor,它負責與 DOM element 進行互動。當使用者在欄位中輸入字元,觸發 input event 的時候, formControl Directive 就會透過 ValueAccessor 取得使用者輸入的值,然後對 FormControl 的 value 進行更新。

反之,當 FormControl 的 value 發生變動時, formControl Directive 會藉由 ValueAccessor 的 writeValue() function,將變動的值寫入 DOM element。

1// DefaultValueAccessor source code
2writeValue(value: any): void {
3  const normalizedValue = value == null ? '' : value;
4  this._renderer.setProperty(this._elementRef.nativeElement, 'value', normalizedValue);
5}

結論

本篇文章介紹 Angular 機制是如何透過建立 FormControl,並且引入 formcontrol Directive 來完成使用者輸入互動和欄位驗證。而這樣的概念也會應用在 formGroupformBuilder 中,往後在寫 Reactive Form 時,就可以更清楚地知道,該如何透過這樣的流程來完成心目中的表單設計。