摘要:由于的属性提供了令牌,并且该令牌指向的对象就是,所以构造函数中注入的令牌包含的对象数组只有一个。这样的构造函数里就会包含一个对象,然后把这个传给对象,最后注册回调,这样以后值更新时就会运行。整个包的设计也是按照这种数据流形式,并不复杂。
我们知道,Angular 的 @angular/forms 包提供了 NgModel 指令,来实现双向绑定,即把一个 JS 变量(假设为 name)与一个 DOM 元素(假设为 input 元素)进行绑定,这样 name 的值发生变化,input 元素 的 value 也会自动变化;input 元素的 value 发生变化,name 的值也会自动变化。如下代码,展示一个最简单的双向绑定(也可见 stackblitz demo):
@Component({ selector: "my-app", template: `{{name}}
`, styleUrls: [ "./app.component.css" ] }) export class AppComponent { name = "banana"; }
上面代码使用了 NgModel 指令来把变量 name 和 input DOM 元素双向绑定到了一起,这里为了更清晰理解 NgModel 的本质,没有使用 [(ngModel)] 语法糖。实际上,在模板里写 [(xxx)] 这种 "BANANA_BOX" 语法,@angular/compiler 的 Template Parser 会把这种语法拆解为为 [xxx] 和 (xxxChange),可看 L448-L453 和 L501-L505,所以 [(xxx)] 仅仅是为了省事的简单写法。
查看 stackblitz demo 可以看到,如果修改 input 里的值,name 变量的值也自动发生变化了,这点可从与 name 绑定的 p 标签值自动变化看出;如果点击 button 修改了 name 的值,input 输入框内的 value 值也发生变化了,这点可从 input 框内的值变化可看到。那 NgModel 指令是如何做到双向绑定的呢?
在理解 NgModel 指令双向绑定原理之前,可以先看看双向绑定最简单形式:
Hello {{country}}!
点击 button 修改 model 时,就会自动修改 input 的 value 值,即自动修改 view,数据流方向就是 model -> view;更新 input 框内值时,就会自动修改 country 这个 model 值,这点可从绑定的 p 标签看到,这时数据流方向就是 view -> model。当然,这是最简单且最不可扩展的一个双向绑定实例,如果去设计一个指令,不仅仅需要考虑 view 的不同类型,而且还需要考虑数据校验问题。尽管如此,这个简单实例与 NgModel 指令本质是类似的。
如果自己设计这样一个双向绑定指令,那它的输入必然是绑定的变量 name,该指令接收 name 后再去更新 input 元素的 value 值(还得支持 textarea,select 等 DOM 元素,甚至组件等自定义 DOM 元素),这样 name 发生变化,input 的 value 也会自动变化,即 model -> view;输出的必然是 input 元素的 value 值,然后赋值给 name,这样 input 元素的值变化,name 值也自动变化,即 view -> model。这里的最难点是该指令得能够写 DOM 元素(不管原生或者自定义 DOM 元素)的值,并且能够监听 DOM 元素的值变化,读取变化的值。 所以,为了支持原生 DOM 元素或自定义 DOM 元素,为了有个好的设计模式,必然会抽象出一个接口,来帮助指令去写入和监听读取 DOM 元素值,有了这个接口,事情就简单很多了。
现在,我们需要搞明白两个问题:name 值发生变化时,input 的 value 如何自动变化;input 的 value 变化,name 值如何自动变化?
绑定到 input 上的 NgModel 指令在实例化时,其 构造函数 会首先查找出 ControlValueAccessor 对象,这个 ControlValueAccessor 就是上文提到的抽象出来的对象,该对象会具体负责更新和监听读取 DOM 元素的值。上文模板中的 input 元素不仅仅绑定了 NgModel 指令,实际上还绑定了 DefaultValueAccessor 指令,这点可以从该指令的选择器知道,如果 input 模板是这么写的:
那不仅仅绑定了 DefaultValueAccessor 指令,还绑定了 NumberValueAccessor 指令。
由于 DefaultValueAccessor 的 providers 属性提供了 NG_VALUE_ACCESSOR 令牌,并且该令牌指向的对象就是 DefaultValueAccessor,所以 NgModel 构造函数中注入的 NG_VALUE_ACCESSOR 令牌包含的 ControlValueAccessor 对象数组只有 DefaultValueAccessor 一个。如果是 type="number" 的 input,则 valueAccessors 包含 NumberValueAccessor 和 DefaultValueAccessor 这两个对象。构造函数中的 selectValueAccessor() 方法会依次遍历 NG_VALUE_ACCESSOR 令牌提供的 ControlValueAccessor 对象数组,如果是自定义的 ControlValueAccessor 优先选择自定义的,如果是 @angular/forms 内置的 ControlValueAccessor 就选择内置的(内置的也就 6 个),否则最后选择默认的 ControlValueAccessor 即 DefaultValueAccessor 对象。对于本文 demo,那就是默认的 DefaultValueAccessor 对象。注意的一点是,注入的 NG_VALUE_ACCESSOR 令牌有装饰器 @Self,所以只能从自身去查找这个依赖,自身的意思是 NgModel 指令自己,和它一起挂载到 input 元素的其他指令。另外,input 上没有绑定任何 validators 指令,所以注入的 NG_VALIDATORS 和 NG_ASYNC_VALIDATORS 令牌解析的值为空,并且 input 多带带使用,没有放在 form 元素内,或 FormGroup 绑定的元素内,所以不存在宿主控件容器 ControlContainer,即 parent 也为空。
NgModel 指令在首次实例化时,运行 _setUpControl() 方法,利用 ControlValueAccessor(本 demo 即 DefaultValueAccessor 对象) 把 NgModel 指令内部的 FormControl 对象与 DOM 元素绑定。由于本 demo 中,NgModel 指令绑定的 input 没有父控件容器,所以会调用 _setUpStandalone 方法,核心方法就是 setUpControl(),该方法主要包含两点:第一点,通过调用 setUpViewChangePipeline() 向 DefaultValueAccessor 对象内注册一个回调函数,这样当 input 值发生变化时,就触发 input 事件 时,会执行这个回调函数,而这个回调函数的逻辑 一是更新 FormControl 的 value,二是让 NgModel 指令抛出 ngModelChange 事件,该事件包含的值就是当前 input 变化的新值,所以,setUpViewChangePipeline() 方法的作用就是搭建了 view -> model 的管道,这样 view (这里是 input) 值发生变化时,会同步 FormControl 对象的 value 值,并让 NgModel 指令把这个新值输出出去;第二点,通过调用 setUpModelChangePipeline 方法向 FormControl 对象内注册 一个回调,这个回调逻辑是当 FormControl 的 value 值发生变化时(本 demo 中就是 [ngModel]="name" 时,name 值发生变化,也就是属性值改变,这样 isPropertyUpdated(changes, this.viewModel) 就为 true,这样就会需要更新 FormControl 的 value 值 FormControl.setValue(value),从而会 触发 上文说的 FormControl 对象内的回调函数),通过调用 ControlValueAccessor.writeValue() 方法去修改 view (这里是 input) 的 value 值(本 demo 中使用的是 DefaultValueAccessor.writeValue(value)),然后让 NgModel 指令抛出 ngModelChange 事件,该事件包含的值就是当前 FormControl 对象 变化的新值,所以,setUpModelChangePipeline() 方法的作用就是搭建了 model -> view 的管道,这样 FormControl 对象值发生改变时,会同步更新 view 的 value,并让 NgModel 指令把这个新值输出出去。
通过以上的解释,就能理解 name 值发生变化时,input 的 value 是如何自动变化的;input 的 value 发生变化时,name 值是如何自动变化的。(最好能一个个点击链接查看源码,效率更高。) 一句话解释就是:NgModel 指令初始化时先安装了两个回调(一个是 view 变化时更新 FormControl 对象 value 值的回调,另一个是 FormControl 对象 value 值变化时更新 view 值的回调),数据流方向从 view -> model 时,更新 FormControl 对象并抛出携带该值的 ngModelChange 事件,数据流方向从 model -> view 时,利用 ControlValueAccessor 去更新 view 值,同时也抛出携带该值的 ngModelChange 事件。抛出的 ngModelChange 事件包含新值,模板中的 $event 会被 @angular/compiler 特殊处理,为 ngModelChange 事件抛出的值。
当然,本文没有考虑存在 Validators 的情况,如果 input 模板修改为如下代码:
那该模板除了绑定 NgModel 指令外,还绑定了 RequiredValidator 指令,这样不管数据流方向是 view -> model 还是 model -> view,在数据流动之前,还需要运行验证器,验证数据的有效性。这样 NgModel 的构造函数里就会包含 一个 RequiredValidator 对象,然后 把这个 Validator 传给 FormControl 对象,最后注册 validatorChange 回调,这样以后 FormControl 值更新时就会 运行 Validators。
总之,NgModel 指令来管理 model <-> view 的数据流,内部存在一个 FormControl 对象,用来读取存储值和验证有效性,从 FormControl 读取的值会赋值给外界传进来的 model,view 是借助 ControlValueAccessor 来读写值。整个 @angular/forms 包的设计也是按照这种数据流形式,并不复杂。
也可阅读 @angular/forms 相关文章了解如何写一个自定义的 ControlValueAccessor:译 别再对 Angular 表单的 ControlValueAccessor 感到迷惑。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/96510.html
摘要:校验器运行完成后,会设置属性,从而计算的属性,假设校验错误,则属性值为。这样就理解了校验器的整个运行过程,也包括为何校验错误时会自动添加描述控件状态的。 我们知道,@angular/forms 包主要用来解决表单问题的,而表单问题非常重要的一个功能就是表单校验功能。数据校验非常重要,不仅仅前端在发请求给后端前需要校验数据,后端对前端发来的数据也需要校验其有效性和逻辑性,尤其在存入数据库...
摘要:目标展现层与逻辑层分离数据与可视化组件相分离数据与视图双向绑定实时更新代码结构清晰易于维护与修改基本原理的组件生命周期钩子方法父子组件交互机制模板语法源码解析代码结构很简单,其中除主页和之外的代码结构如下所示实现宿主视图定义,个按钮,按钮 目标 展现层与逻辑层分离 数据与可视化组件相分离 数据与视图双向绑定,实时更新 代码结构清晰,易于维护与修改 基本原理 angular2 的组件...
摘要:首先,我们需要在入口页面的中配置根路径然后创建一个路由模块路由配置在主模块中导入配置好的路由模块而在页面中需要一个容器去承载上面代码中的定义了用户点击后的路由跳转,定义该路由激活时的样式类。 刚实习的时候用过AngularJS,那时候真的是连原生JavaScript都不会写,依样画葫芦做了几个管理后台。然后突然换项目了,AngularJS就不写了,感觉前前后后接触了一年多的Angula...
摘要:我们使用了模式书写,并引入了思想,这些以前只在里见到的设计,现在里也有体现,并且在本章中会着重讲解多的协作。如果之前写过,那对于这种书写方式一定无比熟悉。每次数据的变更,无论是还是,都将变化冒泡到,然后由再向下逐级推送各组件是否重绘。 前集回顾 在上一章里我们讲了如何在angular2下开发一个component(还没做的赶紧去学吧)。我们使用了Unidirectional Data ...
摘要:在表单上添加的会拦截标准的表单提交事件。并为它们提供了一些共同的行为和属性,其中有些是可观察对象。用于跟踪一个单独的表单控件的值和有效性状态。组件中的顶级表单就是一个。在表单所在的中的上添加,再在指定的验证方法中调用来显示验证失败信息。 angular4 表单 模板表单 在app.module中导入FormsModule之后,项目中的form表单都会是一个ngForm,也就是一个模板表...
阅读 3553·2021-11-04 16:06
阅读 3545·2021-09-09 11:56
阅读 766·2021-09-01 11:39
阅读 863·2019-08-29 15:28
阅读 2261·2019-08-29 15:18
阅读 805·2019-08-29 13:26
阅读 3294·2019-08-29 13:22
阅读 1007·2019-08-29 12:18