
經過前面二十九天的的練習與學習,相信大家應該在表單的實作上都熟悉了不少,只要不是太複雜、太特別的表單應該也都難不倒你們。
今天是本系列文的最後一天,就讓我們來好好地深入了解一下 Angular 表單會這麼強大的原因吧!
首先,我想趁大家記憶猶新時,先帶大家來看為什麼我們昨天可以用 ControlContainer 來自訂一個可以被 Template Driven Forms 或是 Reactive Forms 所使用的 Component 。
ControlContainer

上圖是我根據 Angular 的 Source Code 找出 ControlContainer 的關係所畫的(斜體表抽象類別)。
從圖中我們會發現, ControlContainer 其實只是一個抽象類別,並繼承了另一個抽象類別 AbstractControlDirective ,而 AbstractControlDirective 這個抽象類別其實也被另一個抽象類別 NgControl 所繼承。
NgControl晚點會提到,此處暫不多做說明。
至於 ControlContainer ,它其實也被 AbstractFormGroupDirect 、 NgForm 、 FormGroupDirective 與 FormArrayName 這四個 Directive 所繼承;甚至 AbstractFormGroupDirect 還被 FormGroupName 與 NgModelGroup 這兩個 Directive 所繼承。
換句話說, Angular 根據 ControlContainer 為基底,做出了以下五個 Directive :
FormGroupDirectiveFormGroupNameFormArrayNameNgFormNgModelGroup
在這五個 Directive 裡,前面三個是為什麼我們在用 Reactive Forms 的方式來開發表單時,可以在 Template 裡用 [formGroup] 、 [formGroupName] 、 [formArrayName] 的方式將元素與 FormGroup 、 FormArray 綁定的原因。
大家應該都還記得我們是怎麼將
FormGroup與FormArray綁定到元素上的吧?!
而後面兩個則是為什麼我們在用 Template Driven Forms 的方式來開發表單時,可以在 Template 裡在元素上使用 #XXX="ngForm' 、 #XXX="ngModelGroup" 之後,可以拿到 NgForm 與 NgModelGroup 的實體的原因。
雖然本系列文沒有特別提到
ngModelGroup的用法,想知道的朋友可以參考官方的 NgModelGroup API 文件
那為什麼我們可以使用 ControlContainer 來自訂元件呢?
其實這正是因為上述五個 Directive 都透過 ControlContainer 這個令牌,把自己註冊到 Angular 的 DI 系統裡,讓想使用它們的類別,可以很方便地透過 Angular 的 DI 系統來找到它們的實體。
像是在昨天的文章裡所分享的那樣(Reactive Forms 的方式):
1 | export class AddressInfoComponent { |
Template Driven Forms 則是透過
viewProvider的方式,忘記的話請看昨天的文章。
NgControl

同樣地, Angular 也根據 NgControl 為基底,做出了以下三個 Directive :
FormControlNameFormControlDirectiveNgModel
在這三個 Directive 裡,前面兩個是為什麼我們在用 Reactive Forms 的方式來開發表單時,可以在 Template 裡用 [formControl] 、 [formControlName] 的方式將元素與 FormControl 綁定的原因。
大家應該都還記得我們是怎麼將
FormControl綁定到元素上的吧?!
而最後一個則是為什麼我們在用 Template Driven Forms 的方式來開發表單時,會在 Template 裡在元素上使用 #XXX="ngModel' 之後,可以拿到 NgForm 與 NgModelGroup 的實體的原因。
不過,大家還記不記得我們在第二十八天的時候,是怎麼自訂表單元件的嗎?
沒錯!就是 ControlValueAccessor 。
上述三個 Directive 實作時,也是透過 DI 拿到實作了 ControlValueAccessor 介面的實體,並在初始化的時候透過 ControlValueAccessor 這個介面,搭建 FormControl 與實作了它的實體之間的溝通管道。
想看原始碼的朋友可以點我看原始碼。
如果想知道
@Self與@Optional裝飾器是幹嘛用的,可以參考這篇很前顯易懂的文章: @Self or @Optional @Host? The visual guide to Angular DI decorators.
但除了 ControlValueAccessor 之外,其實 Angular 還有其他內建的 ValueAccessor:

從上圖中我們可以發現,內建的 ValueAccessor 基本上都是繼承於 BaseControlValueAccessor 這個類別,然後分別被 DefaultValueAccessor 與 BuiltInControlValueAccessor 繼承,而只有 DefaultValueAccessor 有實作 ControlValueAccessor 這個介面。
然後 Angular 再基於 BuiltInControlValueAccessor 之上去建立了以下六個 ValueAccessor:
NumberValueAccessorRangeValueAccessorRadioControlValueAccessorCheckboxControlValueAccessorSelectControlValueAccessorSelectMultipleControlValueAccessor
而這六個 ValueAccessor 對於我們的表單開發來說是至關重要的存在,沒有了它們,我們就沒辦法在這些元素上綁定我們的表單控制項。
但其實 BaseControlValueAccessor 與 BuiltInControlValueAccessor 本身並沒有什麼比較特別的實作或定義,之所以會有 DefaultValueAccessor 與 BuiltInControlValueAccessor 的區別,是為了在機制上,能夠做到一個優先權判斷的機制。
當我們在使用自訂的 ValueAccessor 時候, DefaultValueAccessor 或是上述六個 ValueAccessor 其實都有可能與我們自訂的 ValueAccessor 同時存在。
因此,在將我們的表單控制項與元素綁定的時候, Angular 會根據以下的優先權來抓取對應的 ValueAccessor :
- CustomValueAccessor
- BuiltInValueAccessor
- DefaultValueAccessor
如此一來,只要我們沒有自訂 ValueAccessor ,預設就是會使用內建的 ValueAccessor 來搭建表單控制項與元素之間的溝通橋樑。
我覺得 Angular 的開發團隊真的很聰明!
同步與非同步
除了上述的東西之外,其實還有一個比較特別的點,就是關於同步更新與非同步更新的問題。
在 Day16 - Template Driven Forms vs Reactive Forms 的小結裡,我分享了一個表格,表格裡提到了 Template Driven Forms 的可預測性是非同步的。
而這件事情其實可以在 NgModel 的原始碼第 222 行 看出一點端倪。
原始碼第 222 行這行是指 NgModel 在初始化的時候,會去設置表單控制項。
而在原始碼第 268 行中可以看到,它會判斷該 NgModel 是不是 _isStandalone ,也就是它是不是單獨存在,還是有被 <form></form> 包住。
如果是單獨存在, NgModel 的行為其實會跟 FormControlDirective 與 FormControlName 一樣,因為他們會用一樣的方法設置表單控制項。
但如果不是單獨存在,它會用 NgForm 的 addControl 來設置表單控制項,這時,它就會是非同步的,因為他必須等到 resolvedPromise 發出 resolver 事件的時候,才會進行設置。
addControl的實作在原始碼的第 187 行到 196 行
resolvedPromise的定義則是在原始碼的第 27 行
我其實不太懂 Angular 為什麼要在 NgModel 被 <form></form> 包起來的時候這樣子做,因為 resolvedPromise 其實也沒什麼特別的地方,但它們就讓 NgForm 在 addControl 、 removeControl 、 addFormGroup 與 removeFormGroup 這四個方法要變成非同步的方式處理。
如果大家有興趣,或許找個時間研究一下,說不定,你就幫官方解決了一個問題呢!
本日小結
今天主要是幫本系列文做個結尾,希望能讓大家透過我的分享,更熟悉、更了解 Angular 一點。
也因為分享的關係,其實在撰寫本系列文的同時,我也得到了許多。
以前尚不熟悉的更熟悉了;以前不知道的知道了。
這或許就是所謂的「施比受更有福」吧?!
未來,還會不會再寫鐵人賽還不曉得(目前是不想再寫了,哈哈!)。
但是,寫鐵人賽真的是一個非常好的學習機會。
透過撰寫文章,來疏理自己所學,仔細咀嚼後再回饋給社群、回饋給社會。
如果你還沒寫過鐵人賽,我衷心推薦你這一輩子一定至少要寫一次鐵人賽。
最後,我想感謝訂閱我的文章、閱讀我的文章、喜歡我的文章的你們,謝謝你們不嫌棄,也謝謝你們願意讓我能夠幫到你們。
我也想感謝我的家人們,寫鐵人賽的這段期間,真的是非常地疏於陪伴,謝謝他們的支持(雖然他們看不到)。
感謝大家的收看,我們有緣再見! :)










































