在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); // 將更新后的數據通知到外部組件
}
最開始貼出來的代碼,中間使用了set 和get去處理了數據,在get childData()方法里打斷點發現會執行很多次該方法,其實也可以修改成通過更新數據的時候就直接調用change()方法來通知外部組件數據更新,如下:
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;
childData = 1;
add () {
this.childData ++;
this.change(this.childData);
}
change = (value: any) => {}; // 先定義一個方法,很重要,用於接收registerOnChange()方法里傳遞回來的方法,然后通過這個方法就能通知到外部組件數據更新。
writeValue(val): void { // 初始化時,獲取並監聽父組件通過ngModel傳遞進來的數據
if (val) {
this.childData = val;
}
}
registerOnChange(fn: any): void { // 初始化后,執行該方法,並保存控件接收到 change 事件后,調用的函數
this.change = fn;
}
registerOnTouched(fn: any): void {
}
}
中間不用使用get和set,不知道兩種方法哪種更好。
其實通過子組件通知父級組件數據更新,可以使用@Input和@Output來實現的,如果是@Input獲取的父級組件的數據,父級組件數據更新,子組件需要在ngOnChanges生命周期里去監聽對應的數據變更並處理相應的邏輯。
不過在自定義組件上使用ngModel實現數據的雙向綁定還可以用作表單處理上,比如表單模板和表單驗證。
