Angular4.x ngModel 指令详解

用过angular的前端工程师都知道,angular数据是可以双向绑定的。但是它为什么可以使数据双向绑定?原理又是什么?阅读这篇文章,来了解一下吧。

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;
  }
}
(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
ZEROZERO
上一篇 2019年7月2日
下一篇 2019年7月14日

相关推荐

  • Angular 4.x ngModel 双向绑定原理揭秘

    一直以来都没有去深入探究Angular,只是熟练运用。真要被问起来,很多关于angular的理论知识都回答不上来。感觉上学背书的能力已经丧失的差不多了。只能以这样的方式搜集整理出来。

    2019年7月2日
    1.9K
  • Angular环境搭建(Windows 10)

    目前前端开发正处于快速发展阶段,接触angular时,Angular 2刚刚发布,现在第五版也已经发布。由于刚开始没有系统的学习,导致工作中,经常捉襟见肘。现在把自己在工作中踩过的…

    2018年9月5日
    4.0K

发表回复

登录后才能评论