经过前面二十九天的的练习与学习,相信大家应该在表单的实作上都熟悉了不少,只要不是太复杂、太特别的表单应该也都难不倒你们。
今天是本系列文的最後一天,就让我们来好好地深入了解一下 Angular 表单会这麽强大的原因吧!
首先,我想趁大家记忆犹新时,先带大家来看为什麽我们昨天可以用 ControlContainer
来自订一个可以被 Template Driven Forms 或是 Reactive Forms 所使用的 Component 。
上图是我根据 Angular 的 Source Code 找出 ControlContainer
的关系所画的(斜体表抽象类别)。
从图中我们会发现, ControlContainer
其实只是一个抽象类别,并继承了另一个抽象类别 AbstractControlDirective
,而 AbstractControlDirective
这个抽象类别其实也被另一个抽象类别 NgControl
所继承。
NgControl
晚点会提到,此处暂不多做说明。
至於 ControlContainer
,它其实也被 AbstractFormGroupDirect
、 NgForm
、 FormGroupDirective
与 FormArrayName
这四个 Directive 所继承;甚至 AbstractFormGroupDirect
还被 FormGroupName
与 NgModelGroup
这两个 Directive 所继承。
换句话说, Angular 根据 ControlContainer
为基底,做出了以下五个 Directive :
FormGroupDirective
FormGroupName
FormArrayName
NgForm
NgModelGroup
在这五个 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 的方式):
export class AddressInfoComponent {
constructor(private controlContainer: ControlContainer) { }
}
Template Driven Forms 则是透过
viewProvider
的方式,忘记的话请看昨天的文章。
同样地, Angular 也根据 NgControl
为基底,做出了以下三个 Directive :
FormControlName
FormControlDirective
NgModel
在这三个 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:
NumberValueAccessor
RangeValueAccessor
RadioControlValueAccessor
CheckboxControlValueAccessor
SelectControlValueAccessor
SelectMultipleControlValueAccessor
而这六个 ValueAccessor 对於我们的表单开发来说是至关重要的存在,没有了它们,我们就没办法在这些元素上绑定我们的表单控制项。
但其实 BaseControlValueAccessor
与 BuiltInControlValueAccessor
本身并没有什麽比较特别的实作或定义,之所以会有 DefaultValueAccessor
与 BuiltInControlValueAccessor
的区别,是为了在机制上,能够做到一个优先权判断的机制。
当我们在使用自订的 ValueAccessor
时候, DefaultValueAccessor
或是上述六个 ValueAccessor 其实都有可能与我们自订的 ValueAccessor
同时存在。
因此,在将我们的表单控制项与元素绑定的时候, Angular 会根据以下的优先权来抓取对应的 ValueAccessor :
如此一来,只要我们没有自订 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 一点。
也因为分享的关系,其实在撰写本系列文的同时,我也得到了许多。
以前尚不熟悉的更熟悉了;以前不知道的知道了。
这或许就是所谓的「施比受更有福」吧?!
未来,还会不会再写铁人赛还不晓得(目前是不想再写了,哈哈!)。
但是,写铁人赛真的是一个非常好的学习机会。
透过撰写文章,来疏理自己所学,仔细咀嚼後再回馈给社群、回馈给社会。
如果你还没写过铁人赛,我衷心推荐你这一辈子一定至少要写一次铁人赛。
最後,我想感谢订阅我的文章、阅读我的文章、喜欢我的文章的你们,谢谢你们不嫌弃,也谢谢你们愿意让我能够帮到你们。
我也想感谢我的家人们,写铁人赛的这段期间,真的是非常地疏於陪伴,谢谢他们的支持(虽然他们看不到)。
感谢大家的收看,我们有缘再见! :)
>>: [Day30] 第三十 - 总结技能交换系统(整合Laravel以及Express的Microservices)
登入功能不外乎输入帐密、验证、赋予角色功能权限。Vaadin-on-Kotlin 提供了 VoK-S...
前言: 又到了发文的时间了,最近我的屁股一直长疔子真的好痛,有人知道要怎麽治标吗,一直跪着打程序也不...
回顾 & 总结 今天是最後一天了,来回顾一下这些日子分享及学习的内容,虽然是写LeetCod...
在上一章我们提到了丛集是所谓的节点管理者,其中控制平面控制着k8s内所有的节点与资源。 那麽,所谓的...
在还没有担任秘书之前,心底总是对高管秘书有个憧憬:一人之下万人之上,只要打扮的漂漂亮亮,负责帮董事长...