在angular里我们一般都是给input元素添加[(ngModel)]="value"实现数据双向绑定,如果想实现自定义的组件上实现ngModel双向数据绑定应该怎么办呐。。。
网上找了一下,没看懂记录一下。
场景:组件能获取父组件通过ngModel绑定的值,能通过ngModel改变父组件对应的数据。如下代码:
<app-child [(ngModel])="appData"></app-child>
1、先贴出效果图:
2、下面是app-child组件的代码:
import { Component, forwardRef } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @Component({ selector: 'app-child', templateUrl: './child.component.html', styleUrls: ['./child.component.css'], providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ChildComponent), multi: true }] }) export class ChildComponent implements ControlValueAccessor { constructor() { } _data: any; add () { this.childData ++; } change = (value: any) => {}; // 先定义一个方法,很重要,用于接收registerOnChange()方法里传递回来的方法,然后通过这个方法就能通知到外部组件数据更新。 set childData(value: number) { // childData被更改走该方法 this._data = value; this.change(this._data); // 将更新后的数据通知到外部组件 } get childData() { // 页面或者方法里面有调用childData就会走该方法 return this._data; } writeValue(val): void { // 初始化时,获取并监听父组件通过ngModel传递进来的数据 if (val) { this._data = val; } } registerOnChange(fn: any): void { // 初始化后,执行该方法,并保存控件接收到 change 事件后,调用的函数 this.change = fn; } registerOnTouched(fn: any): void { } }
3、下面开始说下实现的过程吧:
如果添加ngModel后报如下错误,检查组件对应的Module文件有没有导入FormsModule
import { FormsModule } from '@angular/forms'; @NgModule({ ... imports: [ ..., FormsModule ], ... })
import FormsModule后,控制台任然会报错:
这是因为我们需要在使用ngModel的组件里实现ControlValueAccessor的接口方法。
先引入和使用我们必须使用的配置:
import { Component, forwardRef } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @Component({ selector: 'app-child', templateUrl: './child.component.html', styleUrls: ['./child.component.css'], providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ChildComponent), // 这里的组件名为当前组件的名字 multi: true }] }) export class ChildComponent implements ControlValueAccessor { constructor() { } childData = 2; }
处理完成后控制台的报错信息已经改变:
这是因为ControlValueAccessor的接口有几个必须存在的方法,会自动去调用:
writeValue(val): void { } registerOnChange(fn: any): void { } registerOnTouched(fn: any): void { }
初始化的时候调用 writeValue() 方法,将会使用表单模型中对应的初始值作为参数(也就是ngModel里的值)。
registerOnChange() 可以用来通知外部,组件已经发生变化。
registerOnTouched() 方法用于设置当控件接收到 touched 事件后,调用的函数。
知道了这三个方法后,我们就可以在writeValue方法里给组件设置父组件通过ngModel传递过来的值了。如:
writeValue(val): void { if (val) { this.childData = val; } }
那么怎么将组件里更新的数据传递给父组件呐。
registerOnChange(fn: any): void { // 初始化后,执行该方法,并保存控件接收到 change 事件后,调用的函数 this.change = fn; }
writeValue()方法后就会执行registerOnChange()方法,我们就是通过该方法传递回来的方法参数来通知到外部组件数据更新的,所以我们要在最开始就定义一个方法来接收。
change = (value: any) => {}; // 先定义一个方法,很重要,用于接收registerOnChange()方法里传递回来的方法,然后通过这个方法就能通知到外部组件数据更新。
然后就可以通过change方法通知外部组件了
set childData(value: number) { // childData被更改走该方法 this._data = value; this.change(this._data); // 将更新后的数据通知到外部组件 }