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--; } }