ControlValueAccessor 是一個鏈接表單模型和視圖(DOM元素)的接口,自定義的表單控件必須實現這個接口,它的做用是:框架
Angular 引入這個接口的緣由是,不一樣的輸入控件數據更新方式是不同的。例如,對於咱們經常使用的文本輸入框來講,咱們是設置它的 value 值,而對於複選框 (checkbox) 咱們是設置它的 checked 屬性。實際上,不一樣類型的輸入控件都有一個 ControlValueAccessor,用來更新視圖ide
Angular 中常見的 ControlValueAccessor 有:函數
首先咱們先看一下 ControlValueAccessor 接口,具體以下:this
export interface ControlValueAccessor { writeValue(obj: any): void; registerOnChange(fn: any): void; registerOnTouched(fn: any): void; setDisabledState?(isDisabled: boolean): void; }
當表單初始化的時候,將會使用表單模型中對應的初始值做爲參數,調用 writeValue() 方法code
<form #form="ngForm"> <exe-counter name="counter" ngModel></exe-counter> <button type="submit">Submit</button> </form>
你會發現,咱們沒有爲 CounterComponent 組件設置初始值,所以咱們要調整一下 writeValue() 中的代碼,具體以下:orm
writeValue(value: any) { if (value) { this.count = value;//接收從表單模型層傳進來的數據 } }
如今,只有當合法值 (非 undefined、null、"") 寫入控件時,它纔會覆蓋默認值。接下來,咱們來實現 registerOnChange() 和 registerOnTouched() 方法。registerOnChange() 能夠用來通知外部,組件已經發生變化。registerOnChange() 方法接收一個 fn 參數,用於設置當控件接收到 change 事件後,調用的函數。而對於 registerOnTouched() 方法,它也支持一個 fn 參數,用於設置當控件接收到 touched 事件後,調用的函數。示例中咱們不打算處理 touched 事件,所以 registerOnTouched() 咱們設置爲一個空函數。具體以下:對象
@Component(...) class CounterComponent implements ControlValueAccessor { ... propagateChange = (_: any) => {}; registerOnChange(fn: any) { this.propagateChange = fn;//每次控件view層的值發生改變,都要調用該方法通知外部 } registerOnTouched(fn: any) {} }
註冊表單控件:token
步驟一:建立Token爲NG_VALUE_ACCESSOR的提供商接口
@Component({ selector: 'exe-counter', ... providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CounterComponent ), multi: true } ] })
步驟二:建立Token爲NG_VALIDATORS的表單控件驗證器事件
@Component({ selector: 'exe-counter', ... providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CounterComponent ), multi: true }, { provide: NG_VALIDATORS, useValue: validateCounterRange, multi: true } ]
CounterComponent 組件的完整代碼以下:
import { Component, Input, forwardRef } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS, AbstractControl, ValidatorFn, ValidationErrors, FormControl } from '@angular/forms'; export const EXE_COUNTER_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CounterComponent), multi: true }; export const validateCounterRange: ValidatorFn = (control: AbstractControl): ValidationErrors => { return (control.value > 10 || control.value < 0) ? { 'rangeError': { current: control.value, max: 10, min: 0 } } : null; }; export const EXE_COUNTER_VALIDATOR = { provide: NG_VALIDATORS, useValue: validateCounterRange, multi: true }; @Component({ selector: 'exe-counter', template: ` <div> <p>當前值: {{ count }}</p> <button (click)="increment()"> + </button> <button (click)="decrement()"> - </button> </div> `, providers: [EXE_COUNTER_VALUE_ACCESSOR, EXE_COUNTER_VALIDATOR] }) export class CounterComponent implements ControlValueAccessor { @Input() _count: number = 0; get count() { return this._count; } set count(value: number) { this._count = value; this.propagateChange(this._count); } propagateChange = (_: any) => { }; writeValue(value: any) { if (value) { this.count = value; } } registerOnChange(fn: any) { this.propagateChange = fn; } registerOnTouched(fn: any) { } increment() { this.count++; } decrement() { this.count--; } }