@angular/forms 源码剖析之双向绑定

我们晓得,Angular 的 @angular/forms 包供应了 NgModel 指令,来完成双向绑定,即把一个 JS 变量(假设为 name)与一个 DOM 元素(假设为 input 元素)举行绑定,如许 name 的值发作变化,input 元素 的 value 也会自动变化;input 元素的 value 发作变化,name 的值也会自动变化。以下代码,展现一个最简朴的双向绑定(也可见 stackblitz demo):

@Component({
  selector: 'my-app',
  template: `
    <input [ngModel]="name" (ngModelChange)="this.name=$event">
    <button (click)="this.name = this.name + ' , apple';">ChangeName</button>
    <p>{{name}}</p>
  `,
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  name = 'banana';
}

上面代码运用了 NgModel 指令来把变量 nameinput DOM 元素双向绑定到了一同,这里为了更清楚邃晓 NgModel 的实质,没有运用 [(ngModel)] 语法糖。实际上,在模板里写 [(xxx)] 这类 ‘BANANA_BOX’ 语法,@angular/compiler 的 Template Parser 会把这类语法拆解为为 [xxx](xxxChange),可看 L448-L453L501-L505,所以 [(xxx)] 仅仅是为了费事的简朴写法。

检察 stackblitz demo 可以看到,假如修正 input 里的值,name 变量的值也自动发作变化了,这点可从与 name 绑定的 p 标签值自动变化看出;假如点击 button 修正了 name 的值,input 输入框内的 value 值也发作变化了,这点可从 input 框内的值变化可看到。那 NgModel 指令是怎样做到双向绑定的呢?

在邃晓 NgModel 指令双向绑定道理之前,可以先看看双向绑定最简朴情势:

<input [value]="country" (input)="country = $event.target.value">
<button (click)="country = country + ' , China';">ChangeCountry</button>
<p>Hello {{country}}!</p>

点击 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 模板是这么写的:

<input [ngModel]="name" (ngModelChange)="this.name=$event" type="number">

那不单单议绑定了 DefaultValueAccessor 指令,还绑定了 NumberValueAccessor 指令。

因为 DefaultValueAccessor 的 providers 属性供应了 NG_VALUE_ACCESSOR 令牌,而且该令牌指向的对象就是 DefaultValueAccessor,所以 NgModel 组织函数中注入的 NG_VALUE_ACCESSOR 令牌包括的 ControlValueAccessor 对象数组只要 DefaultValueAccessor 一个。假如是 type=”number” 的 input,则 valueAccessors 包括 NumberValueAccessorDefaultValueAccessor 这两个对象。组织函数中的 selectValueAccessor() 要领会顺次遍历 NG_VALUE_ACCESSOR 令牌供应的 ControlValueAccessor 对象数组,假如是自定义的 ControlValueAccessor 优先挑选自定义的,假如是 @angular/forms 内置的 ControlValueAccessor 就挑选内置的(内置的也就 6 个),不然末了挑选默许的 ControlValueAccessorDefaultValueAccessor 对象。关于本文 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 模板修正为以下代码:

<input [ngModel]="name" (ngModelChange)="this.name=$event" required>

那该模板除了绑定 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 觉得疑惑

    原文作者:lx1036
    原文地址: https://segmentfault.com/a/1190000015862270
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞