美文网首页angular2Angular 4.x 修仙之路Ionic
Angular 4 ngModel 双向绑定原理揭秘

Angular 4 ngModel 双向绑定原理揭秘

作者: semlinker | 来源:发表于2017-06-04 22:34 被阅读165次

    在 Angular 4.x 中对于使用 Template-Driven 表单场景,如果需要实现表单数据绑定。我们就需要引入 ngModel 指令。该指令用于基于 domain 模型,创建 FormControl 实例,并将创建的实例绑定到表单控件元素上。

    ngModel 使用示例

    ngModel

    app.component.ts

    @Component({
      selector: 'exe-app',
      template: `
       <form novalidate #f="ngForm">
          Name: <input type="text" name="username" ngModel>
       </form>
       {{ f.value | json }}
      `,
    })
    export class AppComponent implements OnInit { }
    

    <form> 表单中使用 ngModel 时,我们需要设置一个 name 属性,以便该控件可以使用该名称在表单中进行注册。

    单向绑定 - [ngModel]

    app.component.ts

    @Component({
      selector: 'exe-app',
      template: `
       <form novalidate #f="ngForm">
          Name: <input type="text" name="username" [ngModel]="user.username">
       </form>
       {{ user | json }}
      `,
    })
    export class AppComponent implements OnInit {
      user: { username: string };
    
      ngOnInit() {
        this.user = { username: 'Semlinker' };
      }
    }
    

    双向绑定 - [(ngModel)]

    表单中应用

    app.component.ts

    @Component({
      selector: 'exe-app',
      template: `
       <form novalidate #f="ngForm">
          Name: <input type="text" name="username" [(ngModel)]="user.username">
       </form>
       {{ user | json }}
      `,
    })
    export class AppComponent implements OnInit {
      user: { username: string };
    
      ngOnInit() {
        this.user = { username: 'Semlinker' };
      }
    }
    

    单独应用

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'exe-app',
      template: `
        <input name="username" [(ngModel)]="username">
        {{username}}
      `,
    })
    export class AppComponent {
      username: string;
    }
    

    ngModelOptions - [ngModelOptions]

    当你在使用 ngModel 时未设置 name 属性,如下所示:

    <form novalidate #f="ngForm">
       Name: <input type="text" [(ngModel)]="user.username">
    </form>
    

    当你运行时,浏览器控制台将会抛出以下异常信息:

    Error: If ngModel is used within a form tag, either the name attribute must be set or the form control must be defined as 'standalone' in ngModelOptions.
    

    以上异常信息告诉我们,如果在表单标签中使用 ngModel,则必须设置 name 属性,或者在 ngModelOptions 中必须将表单控件定义为 "standalone"。依据上述异常信息,我们做如下调整:

    <form novalidate #f="ngForm">
       Name: <input type="text" [(ngModel)]="user.username" 
                [ngModelOptions]="{standalone: true}">
    </form>
    

    接下来我们看一下 ngModelOptions 支持的对象类型:

    @Input('ngModelOptions') options: {name?: string, standalone?: boolean};
    

    禁用控件 - disabled

    <form novalidate #f="ngForm">
       Name: <input type="text" name="username" 
                [(ngModel)]="user.username" disabled="true">
    </form>
    

    监听 ngModelChange 事件 - (ngModelChange)

    app.component.ts

    @Component({
      selector: 'exe-app',
      template: `
       <form novalidate #f="ngForm">
          Name: <input type="text" name="username" (ngModelChange)="userNameChange($event)"
            [(ngModel)]="user.username">
       </form>
       {{ user | json }}
      `,
    })
    export class AppComponent implements OnInit {
      user: { username: string };
    
      ngOnInit() {
        this.user = { username: 'Semlinker' };
      }
    
      userNameChange(name: string) {
        console.log(name);
      }
    }
    

    获取关联的 NgModel 对象

    app.component.ts

    @Component({
      selector: 'exe-app',
      template: `
       <form novalidate #f="ngForm">
          Name: <input type="text" name="username" #userName="ngModel"
            [(ngModel)]="user.username">
       </form>
       {{ userName.control | json }}
      `,
    })
    export class AppComponent implements OnInit {
      user: { username: string };
    
      ngOnInit() {
        this.user = { username: 'Semlinker' };
      }
    }
    

    通过使用 userName="ngModel" 方式,我们可以获取表单控件关联的 NgModel 对象,进而获取控件当前控件的相关信息,如控件的当前的状态或控件验证信息等。

    完整示例

    import { Component } from '@angular/core';
    import { NgForm } from '@angular/forms';
    
    @Component({
      selector: 'exe-app',
      template: `
        <form #f="ngForm" (ngSubmit)="onSubmit(f)" novalidate>
          <input name="first" ngModel required #first="ngModel">
          <input name="last" ngModel>
          <button>Submit</button>
        </form>
        <p>First name value: {{ first.value }}</p>
        <p>First name valid: {{ first.valid }}</p>
        <p>Form value: {{ f.value | json }}</p>
        <p>Form valid: {{ f.valid }}</p>
      `,
    })
    export class AppComponent {
      onSubmit(f: NgForm) {
        console.log(f.value);  // { first: '', last: '' }
        console.log(f.valid);  // false
      }
    }
    

    ngModel 指令详解

    ngModel 指令定义

    @Directive({
      selector: '[ngModel]:not([formControlName]):not([formControl])',
      providers: [formControlBinding],
      exportAs: 'ngModel'
    })
    

    formControlBinding 定义

    export const formControlBinding: any = {
      provide: NgControl,
      useExisting: forwardRef(() => NgModel)
    };
    

    相关说明

    • selector 中 [ngModel]:not([formControlName]):not([formControl]) 表示该指令只应用于 Template-Driven 表单中。
    • exportAs - 表示可以使用 first="ngModel" 语法获取 NgModel 对象

    ngModel 指令输入与输出属性

    输入属性

    @Input() name: string;
    @Input('disabled') isDisabled: boolean;
    @Input('ngModel') model: any;
    @Input('ngModelOptions') options: {name?: string, standalone?: boolean};
    

    输出属性

    @Output('ngModelChange') update = new EventEmitter();
    

    NgModel 类

    // angular2/packages/forms/src/directives/ng_model.ts
    export class NgModel extends NgControl implements OnChanges,
        OnDestroy {
      /** @internal */
      _control = new FormControl(); // 创建FormControl对象
      /** @internal */
      _registered = false; // 用于标识控件是否已注册
      viewModel: any; // 用于保存前一次model的值
      ...
    }
    

    NgModel 构造函数

    constructor(
      @Optional() @Host() parent: ControlContainer,
      @Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>,
      @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators:     
         Array<AsyncValidator|AsyncValidatorFn>,
      @Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
         valueAccessors: ControlValueAccessor[]) {
             super();
             this._parent = parent;
             this._rawValidators = validators || [];
             this._rawAsyncValidators = asyncValidators || [];
             this.valueAccessor = selectValueAccessor(this, valueAccessors);
    }
    

    相关说明

    • @Optional() - 表示该依赖对象是可选的
    • @Host() - 表示从宿主元素注入器获取依赖对象
    • @Self() - 表示从当前注入器获取依赖对象
    • @Inject() - 用于注入 Token (new InjectionToken) 对应的非 Type 类型依赖对象
    • 构造函数执行的操作:
      • 获取 ControlContainer (控件容器)对象
      • 获取控件上的同步验证器
      • 获取控件上的异步验证器
      • 获取控件上的 ControlValueAccessor

    NgModel 生命周期钩子

    ngOnChanges

    ngOnChanges(changes: SimpleChanges) {
        this._checkForErrors(); 
        if (!this._registered) this._setUpControl(); 
        if ('isDisabled' in changes) {
           this._updateDisabled(changes);
        }
        
        if (isPropertyUpdated(changes, this.viewModel)) {
            this._updateValue(this.model);
            this.viewModel = this.model;
        }
    }
    

    _checkForErrors()

    private _checkForErrors(): void {
       if (!this._isStandalone()) {
          this._checkParentType();
       }
       this._checkName();
    }
    
    // 判断是否设置standalone属性
    private _isStandalone(): boolean { 
       return !this._parent || (this.options && this.options.standalone);
    }
    
    /**
     * 1.ngModel指令不能与formGroupName或formArrayName指令一起使用,需改用   
     * formControlName或调整ngModel的父控件使用的指令为ngModelGroup。
     *
     * 2.ngModel不能被注册到使用formGroup指令的表单中,需改用formControlName或设置  
     * ngModelOptions对象中的standalone属性,避免注册该控件。
     */
    private _checkParentType(): void {
       if (!(this._parent instanceof NgModelGroup) &&
          this._parent instanceof AbstractFormGroupDirective) {
             TemplateDrivenErrors.formGroupNameException();
       } else if (!(this._parent instanceof NgModelGroup) && 
          !(this._parent instanceof NgForm)) {
             TemplateDrivenErrors.modelParentException();
       }
    }
    
    /**
     * 验证是否设置name属性
     * 
     * 如果在表单标签中使用 ngModel,则必须设置 name 属性,或者在ngModelOptions中必须将
     * 表单控件定义为"standalone"。
     *
     * <input [(ngModel)]="person.firstName" [ngModelOptions]="{standalone: 
     *    true}">
     */
    private _checkName(): void {
       if (this.options && this.options.name) this.name = this.options.name;
       if (!this._isStandalone() && !this.name) {
             TemplateDrivenErrors.missingNameException();
       }
    }
    

    _setUpControl()

    // 初始化控件
    private _setUpControl(): void {
        this._isStandalone() ? this._setUpStandalone() :
              // 在ControlContainer所属的form中注册该控件
              this.formDirective.addControl(this); 
        this._registered = true; // 标识已注册
    }
    
    // 若设置standalone属性,则初始化该控件,并更新控件的值和验证状态
    private _setUpStandalone(): void {
       setUpControl(this._control, this);
       this._control.updateValueAndValidity({emitEvent: false});
    }
    
    // 获取ControlContainer所属的form
    get formDirective(): any { 
      return this._parent ? this._parent.formDirective : null; 
    }
    

    _updateDisabled()

    若设置 isDisabled 输入属性,则更新控件的 disabled 属性:

    // 更新控件的disabled状态
    private _updateDisabled(changes: SimpleChanges) {
      // 获取disabled输入属性的当前值
      const disabledValue = changes['isDisabled'].currentValue; 
      // 判断是否设置为disabled
      const isDisabled = disabledValue === '' || 
            (disabledValue && disabledValue !== 'false');
    
      resolvedPromise.then(() => {
          if (isDisabled && !this.control.disabled) {
               this.control.disable(); // 禁用控件
          } else if (!isDisabled && this.control.disabled) {
               this.control.enable(); // 启用控件
            }
       });
    }
    

    isPropertyUpdated()

    // 判断属性是否更新
    export function isPropertyUpdated(changes: {[key: string]: any},
      viewModel: any): boolean {
        if (!changes.hasOwnProperty('model')) return false; // @Input('ngModel') model: any;
        const change = changes['model'];
    
        if (change.isFirstChange()) return true; // 判断是否首次改变
        return !looseIdentical(viewModel, change.currentValue);
    }
    
    // JS has NaN !== NaN
    export function looseIdentical(a: any, b: any): boolean {
      return a === b || typeof a === 'number' && typeof b === 'number' && isNaN(a) 
        && isNaN(b);
    }
    

    _updateValue()

    // 更新控件的值
    private _updateValue(value: any): void {
       resolvedPromise.then(
         () => { this.control.setValue(value, {emitViewToModelChange: false}); 
       });
    }
    
    const resolvedPromise = Promise.resolve(null);
    

    ngOnDestroy()

    // 指令销毁时,从formDirective中移除该控件
    ngOnDestroy(): void { 
        this.formDirective && this.formDirective.removeControl(this); 
    }
    

    NgModel 方法

    get control(): FormControl

    // 获取控件
    get control(): FormControl { return this._control; }
    
    /** @internal */
    _control = new FormControl();
    

    get path(): string[]

    // 获取控件的访问路径
    get path(): string[] {
        return this._parent ? controlPath(this.name, this._parent) : [this.name];
    }
    

    get validator(): ValidatorFn

    // 获取同步验证器
    get validator(): ValidatorFn { 
        return composeValidators(this._rawValidators); 
    }
    
    export interface ValidatorFn { (c: AbstractControl): ValidationErrors|null; }
    

    get asyncValidator(): AsyncValidatorFn

    // 获取异步验证器
    get asyncValidator(): AsyncValidatorFn {
       return composeAsyncValidators(this._rawAsyncValidators);
    }
    
    export interface AsyncValidatorFn {
      (c: AbstractControl): Promise<ValidationErrors|null>|Observable<ValidationErrors|null>;
    }
    

    viewToModelUpdate(newValue: any): void

    // 触发ngModelChange事件
    viewToModelUpdate(newValue: any): void {
       this.viewModel = newValue;
       // @Output('ngModelChange') update = new EventEmitter();
       this.update.emit(newValue);
    }
    

    NgControl 抽象类

    // angular2/packages/forms/src/directives/ng_control.ts
    
    // 所有控件指令都需继承的基类,绑定FormControl对象至DOM元素
    export abstract class NgControl extends AbstractControlDirective {
      /** @internal */
      _parent: ControlContainer = null;
      name: string = null;
      valueAccessor: ControlValueAccessor = null;
      /** @internal */
      _rawValidators: Array<Validator|ValidatorFn> = [];
      /** @internal */
      _rawAsyncValidators: Array<AsyncValidator|AsyncValidatorFn> = [];
    
      get validator(): ValidatorFn { return <ValidatorFn>unimplemented(); }
      get asyncValidator(): AsyncValidatorFn { return <AsyncValidatorFn>unimplemented(); }
    
      abstract viewToModelUpdate(newValue: any): void;
    }
    

    AbstractControlDirective 抽象类

    // angular2/packages/forms/src/directives/abstract_control_directive.ts
    export abstract class AbstractControlDirective {
      
      // 获取控件
      get control(): AbstractControl { throw new Error('unimplemented'); }
     
      // 获取控件的值
      get value(): any { return this.control ? this.control.value : null; }
    
      // 控件控件的验证状态 - valid、invalid、pending
      get valid(): boolean { return this.control ? this.control.valid : null; }
    
      get invalid(): boolean { return this.control ? this.control.invalid : null; }
    
      get pending(): boolean { return this.control ? this.control.pending : null; }
      
      get pristine(): boolean { return this.control ? this.control.pristine : null; }
    
      get dirty(): boolean { return this.control ? this.control.dirty : null; }
    
      get touched(): boolean { return this.control ? this.control.touched : null; }
    
      get untouched(): boolean { return this.control ? this.control.untouched : null; }
    
      get disabled(): boolean { return this.control ? this.control.disabled : null; }
    
      get enabled(): boolean { return this.control ? this.control.enabled : null; }
    
      // 获取控件验证异常对象
      get errors(): ValidationErrors|null { 
        return this.control ? this.control.errors : null; 
      }
        
      // 获取statusChanges对象
      get statusChanges(): Observable<any> { 
        return this.control ? this.control.statusChanges : null; 
      }
        
      // 获取valueChanges对象
      get valueChanges(): Observable<any> { 
        return this.control ? this.control.valueChanges : null; 
      }
    
      // 获取控件路径
      get path(): string[] { return null; }
    
      // 重设控件的值
      reset(value: any = undefined): void {
        if (this.control) this.control.reset(value);
      }
    
     // 判断是否path路径对应的控件,是否存在errorCode对应的错误
      hasError(errorCode: string, path: string[] = null): boolean {
        return this.control ? this.control.hasError(errorCode, path) : false;
      }
    
     // 获取path路径对应的控件,参数errorCode对应的错误
      getError(errorCode: string, path: string[] = null): any {
        return this.control ? this.control.getError(errorCode, path) : null;
      }
    }
    

    input 指令

    input 指令定义

    @Directive({
      selector:`
        input:not([type=checkbox])[formControlName],textarea[formControlName],
        input:not([type=checkbox])[formControl],textarea[formControl],
        input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]
     `,
      host: {
        '(input)': '_handleInput($event.target.value)',
        '(blur)': 'onTouched()',
        '(compositionstart)': '_compositionStart()',
        '(compositionend)': '_compositionEnd($event.target.value)'
      },
      providers: [DEFAULT_VALUE_ACCESSOR]
    })
    

    相关说明

    • compositionstart - 事件触发于一段文字的输入之前 (类似于 keydown 事件,但是该事件仅在若干可见字符的输入之前,而这些可见字符的输入可能需要一连串的键盘操作、语音识别或者点击输入法的备选词)。
    • compositionend - 事件触发于完成文本段落输入或取消输入

    compositionstart、compositionend 的实际应用,请参考 - 应对中文输入法的字符串截断方案

    DEFAULT_VALUE_ACCESSOR

    export const DEFAULT_VALUE_ACCESSOR: any = {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => DefaultValueAccessor),
      multi: true
    };
    

    DefaultValueAccessor

    export class DefaultValueAccessor implements ControlValueAccessor {
      onChange = (_: any) => {};
      onTouched = () => {};
    
      /** Whether the user is creating a composition string (IME events). */
      private _composing = false;
    
      constructor(
          private _renderer: Renderer, // 注入Renderer对象
          private _elementRef: ElementRef,
          @Optional() @Inject(COMPOSITION_BUFFER_MODE) 
            private _compositionMode: boolean) {
              if (this._compositionMode == null) {
                this._compositionMode = !_isAndroid();
              }
      }
    
      // 将模型中的新值写入视图或DOM元素属性中
      writeValue(value: any): void {
        const normalizedValue = value == null ? '' : value;
        this._renderer.setElementProperty(this._elementRef.nativeElement, 
             'value', normalizedValue);
      }
    
      // 设置当控件接收到change事件后,调用的函数
      registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
      
      // 设置当控件接收到touched事件后,调用的函数
      registerOnTouched(fn: () => void): void { this.onTouched = fn; }
    
      // 设置控件的Disabled状态
      setDisabledState(isDisabled: boolean): void {
        this._renderer.setElementProperty(this._elementRef.nativeElement, 
          'disabled', isDisabled);
      }
    
      // 处理input事件
      _handleInput(value: any): void {
        if (!this._compositionMode || (this._compositionMode && !this._composing)) {
          this.onChange(value);
        }
      }
    
      // 处理compositionstart事件
      _compositionStart(): void { this._composing = true; }
    
      // 处理compositionend事件
      _compositionEnd(value: any): void {
        this._composing = false;
        this._compositionMode && this.onChange(value);
      }
    }
    
    export const COMPOSITION_BUFFER_MODE = new InjectionToken<boolean>
      ('CompositionEventMode');
    
    // 用于判断是否处于安卓平台,composition事件在iOS和Android存在兼容性
    function _isAndroid(): boolean {
      const userAgent = getDOM() ? getDOM().getUserAgent() : '';
      return /android (\d+)/.test(userAgent.toLowerCase());
    }
    

    相关说明

    为了能够支持跨平台,Angular 通过抽象层封装了不同平台的差异,统一了 API 接口。如定义了抽象类 Renderer 、抽象类 RootRenderer 等。此外还定义了以下引用类型:ElementRef、TemplateRef、ViewRef 、ComponentRef 和 ViewContainerRef 等。

    了解详细的信息,请查看 - Angular 2 ElementRef

    另外看完上面的代码,不知道读者有没有以下的疑问:

    • writeValue() 方法什么时候调用?
    • registerOnChange() 什么时候调用?
    • registerOnTouched() 什么时候调用?

    为了解开这些疑惑我们就需要分析一下,一个很重要的方法 - setUpControl()。我们先来看一下 setUpControl() 的调用的时机点:

    NgModel ngOnChanges 生命周期钩子

    ngOnChanges(changes: SimpleChanges) {
        ...
        if (!this._registered) this._setUpControl(); 
        ...
    }
    

    _setUpControl() 方法

    private _setUpControl(): void {
        this._isStandalone() ? this._setUpStandalone() :
              // 在ControlContainer所属的form中注册该控件
              this.formDirective.addControl(this); 
        this._registered = true; // 标识已注册
    }
    

    _setUpControl() 方法内部,先判断控件有设置 standalone 属性,如果有的话,则调用 _setUpStandalone() 方法:

    // 若设置standalone属性,则初始化该控件,并更新控件的值和验证状态
    private _setUpStandalone(): void {
       setUpControl(this._control, this); // 调用时机点一
       this._control.updateValueAndValidity({emitEvent: false});
    }
    

    如果没有设置 standalone 属性,则调用 this.formDirective.addControl(this),这个方法存在于我们的 form 指令中,我们直接看一下具体实现:

    addControl(dir: NgModel): void {
      resolvedPromise.then(() => {
      const container = this._findContainer(dir.path);
      dir._control = <FormControl>container.registerControl(dir.name, dir.control);
      setUpControl(dir.control, dir); // 调用时机点二
      dir.control.updateValueAndValidity({emitEvent: false});
      });
    }
    

    搞清楚 setUpControl() 调用的时机点,是时候分析一下 setUpControl() 方法的具体实现了。

    setUpControl()

    // angular2/packages/forms/src/directives/shared.ts
    export function setUpControl(control: FormControl, dir: NgControl): void {
        
      if (!control) _throwError(dir, 'Cannot find control with');
      /**
       * NgModel构造函数
       * @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]
       * this.valueAccessor = selectValueAccessor(this, valueAccessors);
       */
      // 判断控件是否实现ControlValueAccessor接口
      if (!dir.valueAccessor) _throwError(dir, 'No value accessor for form control with');
    
      // 组合同步验证器
      control.validator = Validators.compose([control.validator, dir.validator]);
      // 组合异步验证器
      control.asyncValidator = Validators.composeAsync([control.asyncValidator, 
        dir.asyncValidator]);
      
      // 该方法用于将模型中的新值写入视图或 DOM 属性中
      dir.valueAccessor.writeValue(control.value);
    
      // view -> model
      /**
       * @Directive({
       *    selector: 'input:not([type=checkbox])[formControlName],...',
       *    host: {
       *     '(input)': '_handleInput($event.target.value)'
       *    },
       *    providers: [DEFAULT_VALUE_ACCESSOR]
       *  })
       * export class DefaultValueAccessor implements ControlValueAccessor {
       *    // 下面就是调用该方法
       *    registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
       *     
       *    // input事件触发后,调用该方法
       *    _handleInput(value: any): void {
       *      if (!this._compositionMode || (this._compositionMode && !this._composing)) {
       *         this.onChange(value); //调用下面注册的onChange函数
       *      }
       *    }
       * }
       *
       */
      dir.valueAccessor.registerOnChange((newValue: any) => {
        
        /**
         * ngModel指令 - viewToModelUpdate() 方法
         * 
         * viewToModelUpdate(newValue: any): void {
         *    this.viewModel = newValue; // 更新viewModel
         * // @Output('ngModelChange') update = new EventEmitter();
         *    this.update.emit(newValue); // 触发ngModelChange事件
         * }
         */
        dir.viewToModelUpdate(newValue);
        control.markAsDirty();
        /*
        * setValue(value: any, {onlySelf, emitEvent, emitModelToViewChange, 
        *    emitViewToModelChange}: {
        *    onlySelf?: boolean,
        *    emitEvent?: boolean,
        *    emitModelToViewChange?: boolean,
        *    emitViewToModelChange?: boolean
        * } = {}): void {
        *    this._value = value;
        *    if (this._onChange.length && emitModelToViewChange !== false) {
        *       this._onChange.forEach((changeFn) => changeFn(this._value,  
        *           emitViewToModelChange !== false));
        *     }
        *    this.updateValueAndValidity({onlySelf, emitEvent});
        * }
        */
        control.setValue(newValue, {emitModelToViewChange: false}); // 更新控件的值
      });
    
      // touched
      dir.valueAccessor.registerOnTouched(() => control.markAsTouched());
    
      /**
       * control = new FormControl();
       * 
       * control - _onChange 属性
       * _onChange: Function[] = []; 
       *
       * control - registerOnChange() 方法  
       * registerOnChange(fn: Function): void { this._onChange.push(fn); }
      */
      control.registerOnChange((newValue: any, emitModelEvent: boolean) => {
        
        // control -> view
        /*
        * writeValue(value: any): void {
        *   const normalizedValue = value == null ? '' : value;
        *   this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', 
        *       normalizedValue);
        * }
        */
        dir.valueAccessor.writeValue(newValue);
    
        // control -> ngModel
        /**
         * ngModel指令 - viewToModelUpdate() 方法
         * 
         * viewToModelUpdate(newValue: any): void {
         *    this.viewModel = newValue; // 更新viewModel
         * // @Output('ngModelChange') update = new EventEmitter();
         *    this.update.emit(newValue); // 触发ngModelChange事件
         * }
         */
        if (emitModelEvent) dir.viewToModelUpdate(newValue);
      });
    
      // 当控件状态变成 DISABLED 或从 DISABLED 状态变化成 ENABLE 状态时,会调用该函数。该函数会根据参数  
      // 值,启用或禁用指定的 DOM 元素
      if (dir.valueAccessor.setDisabledState) {
        control.registerOnDisabledChange(
            (isDisabled: boolean) => { dir.valueAccessor.setDisabledState(isDisabled); });
      }
    
      // re-run validation when validator binding changes, e.g. minlength=3 -> minlength=4
      dir._rawValidators.forEach((validator: Validator | ValidatorFn) => {
        if ((<Validator>validator).registerOnValidatorChange)
          (<Validator>validator).registerOnValidatorChange(() => 
            control.updateValueAndValidity());
      });
    
      dir._rawAsyncValidators.forEach((validator: AsyncValidator | AsyncValidatorFn) => {
        if ((<Validator>validator).registerOnValidatorChange)
          (<Validator>validator).registerOnValidatorChange(() => 
            control.updateValueAndValidity());
      });
    }
    

    最后我们再看一下 ControlValueAccessor 接口:

    ControlValueAccessor

    // angular2/packages/forms/src/directives/control_value_accessor.ts 
    export interface ControlValueAccessor {
      writeValue(obj: any): void;
      registerOnChange(fn: any): void;
      registerOnTouched(fn: any): void;
      setDisabledState?(isDisabled: boolean): void;
    }
    
    • writeValue(obj: any):该方法用于将模型中的新值写入视图或 DOM 属性中
    • registerOnChange(fn: any):设置当控件接收到 change 事件后,调用的函数
    • registerOnTouched(fn: any):设置当控件接收到 touched 事件后,调用的函数
    • setDisabledState?(isDisabled: boolean):当控件状态变成 DISABLED 或从 DISABLED 状态变化成 ENABLE 状态时,会调用该函数。该函数会根据参数值,启用或禁用指定的 DOM 元素

    了解 ControlValueAccessor 的详细信息,可以参考 - Understanding ControlValueAccessor

    参考资源

    相关文章

      网友评论

        本文标题:Angular 4 ngModel 双向绑定原理揭秘

        本文链接:https://www.haomeiwen.com/subject/tmpbfxtx.html