在Angular2中的数据流动是单向的,我们常见的双向绑定的例子如下:
<input [(ngModel)]="value"/>
等价于
<input [ngModel]="value" (ngModelChange)="valueChange($event)"/>
那么我们如何实现自定义自己的组件,该组件也可以接受[(ngModel)]来实现双向绑定呢?首先来看组件的定义代码:
@Component({
selector: 'app-two-way',
template: `<input [(ngModel)]="value">`,
styleUrls: ['./two-way.component.css'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TwoWayComponent),
multi: true,
}],
})
export class TwoWayComponent implements ControlValueAccessor {
private innerValue: any = '';
private onTouchedCallback: () => void = noop;
private onChangeCallback: (_: any) => void = noop;
get value(): any {
return this.innerValue;
};
set value(v: any) {
if (v !== this.innerValue) {
this.innerValue = v;
this.onChangeCallback(v);
}
}
writeValue(value: any): void {
if (value !== this.innerValue) {
this.innerValue = value;
}
}
registerOnChange(fn: any): void {
this.onChangeCallback = fn;
}
registerOnTouched(fn: any): void {
this.onTouchedCallback = fn;
}
}
这样在template中使用该组件的时候就可以类似于input标签一样可以接受ngModel和ngModelChange了。下面来解释其中使用的东西:
- NG_VALIDATORS:为form control提供ControlValueAccessor,所以我们的组件要实现ControlValueAccessor接口。
- useExisting:避免出现多个provider
- forwardRef:在依赖注入中,每个组件都需要有个与其关联的token,可是在provider注册的时候,我们的组件还没有初始化。所以需要使用forwardRef来告知provider构造器这些,使其可以等组件初始化。
接下来就是实现ControlValueAccessor接口,主要的函数是:
- writeValue:将外部传递进来的值写到模板中
- registerOnChange:该函数接收一个回调函数,用于在数据发生变化时告知外面的世界。
在组件定义中,我们定义了onTouchedCallback和onChangeCallback,但是方法都是没有任何实现的,这个定义时必须的。而且这两个方法的真正实现都会在组件初始化时由Angular自己来提供。所以我们不必担心这两个方法没有任何实现的。