Angular 4.x ngModel 雙向綁定原理揭祕

查看新版教程,請訪問前端修仙之路html

在 Angular 4.x 中對於使用 Template-Driven 表單場景,若是須要實現表單數據綁定。咱們就須要引入 ngModel 指令。該指令用於基於 domain 模型,建立 FormControl 實例,並將建立的實例綁定到表單控件元素上。前端

ngModel 使用示例

ngModel

app.component.tsreact

@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 屬性,以便該控件可使用該名稱在表單中進行註冊。android

單向綁定 - [ngModel]

app.component.tsgit

@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.tsgithub

@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 屬性,以下所示:typescript

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

當你運行時,瀏覽器控制檯將會拋出如下異常信息:json

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"。依據上述異常信息,咱們作以下調整:bootstrap

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

接下來咱們看一下 ngModelOptions 支持的對象類型:segmentfault

@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

參考資源

Resources

Frameworks

Blogs

Tools

Platforms

相關文章
相關標籤/搜索