angular在Form模塊提供了ngModel指令,應用了此指令的表單元素(input,select等)可以支持數(shù)據(jù)的雙向綁定疫萤。當ngModel指令應用在非表單元素上時會在運行時報錯先朦,也就是說ngModel的使用是有前置條件的缰冤。在web應用中,有時候也希望自己開發(fā)的組件(比如日期烙无、多選等)可以支持雙向綁定锋谐,那現(xiàn)在就研究下如何讓自定義的組件也支持雙向數(shù)據(jù)綁定。
先從ngModel指令開始說起截酷,構(gòu)造函數(shù)如下涮拗。
//ngModel構(gòu)造函數(shù)
constructor(parent: ControlContainer, validators: Array<Validator | ValidatorFn>, asyncValidators: Array<AsyncValidator | AsyncValidatorFn>, valueAccessors: ControlValueAccessor[]);
angular的注入器會為構(gòu)造函數(shù)中注入以下實例:
- parent - 父容器
- validators - 同步驗證器
- asyncValidators - 異步驗證器
- valueAccessors - 類型為ControlValueAccessor的數(shù)組
可以猜測雙向綁定依賴于ControlValueAccessor, 通過valueAccessors 可以實現(xiàn)數(shù)據(jù)的雙向流動。查看ControlValueAccessor的相關(guān)文檔:ControlValueAccessor是連接dom元素與form api的橋梁三热,如果創(chuàng)建自定義的form指令(組件也是指令的一種)并與form交互可實現(xiàn)此接口鼓择。接口方法如下:
interface ControlValueAccessor {
writeValue(obj: any): void
registerOnChange(fn: any): void
registerOnTouched(fn: any): void
setDisabledState(isDisabled: boolean)?: void
}
- writeValue - 寫入值,data從model流向view
- registerOnChange - 注冊值改變后的回調(diào)
- registerOnTouched - 注冊失去焦點事件后的回調(diào)
- setDisabledState - angular會在control的disabled狀態(tài)改變時調(diào)用此方法
在angualr的Form模塊中找下實現(xiàn)了ControlValueAccessor的類就漾∧拍埽可以發(fā)現(xiàn)DefaultValueAccessor, RadioControlValueAccessor, NumberValueAccessor, SelectControlValueAccessor, SelectMultipleControlValueAccessor,這些類均實現(xiàn)了ControlValueAccessor 接口抑堡。
在源碼中查看DefaultValueAccessor摆出,類聲名的相關(guān)代碼如下:
export const DEFAULT_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DefaultValueAccessor),
multi: true
};
@Directive({
selector:
'input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]',
host: {
'(input)': '$any(this)._handleInput($event.target.value)',
'(blur)': 'onTouched()',
'(compositionstart)': '$any(this)._compositionStart()',
'(compositionend)': '$any(this)._compositionEnd($event.target.value)'
},
providers: [DEFAULT_VALUE_ACCESSOR]
})
export class DefaultValueAccessor implements ControlValueAccessor {}
看到這里,差不多就可以弄清楚如何實現(xiàn)一個支持雙向綁定的指令了首妖。兩個關(guān)鍵點:
- 指令需要實現(xiàn)ContrlValueAccessor接口
- 為指令提供ACCESSOR偎漫,以便注入器在ngModel中可注入accessor實例執(zhí)行雙向綁定
接下來,就照著DefaultValueAccessor 實現(xiàn)一個支持雙向綁定的組件有缆。之前項目中象踊,寫過一個jquery版本的分布插件,這里實現(xiàn)的angular組件就以分頁為例(需要bootstrap)棚壁。分頁組件要實現(xiàn)以下功能:
- 支持雙向數(shù)據(jù)綁定
- 可以顯示分頁信息
- 頁碼改變后回調(diào)
pager.component.ts雙向綁定相關(guān)代碼如下:
import { Component,forwardRef, Input, Output, ViewChild, EventEmitter,ViewEncapsulation } from '@angular/core';
import {ControlValueAccessor,NG_VALUE_ACCESSOR} from '@angular/forms';
export const PAGER_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => PagerComponent),
multi: true
};
@Component({
selector: 'upc-pager',
templateUrl: './pager.component.html',
encapsulation:ViewEncapsulation.None,
styleUrls:['./pager.component.css'],
providers:[PAGER_CONTROL_VALUE_ACCESSOR]
})
export class PagerComponent implements ControlValueAccessor {
writeValue(v: any): void {
this.currentEle.nativeElement.value = v;
}
registerOnChange(fn: any): void {
this._onChange=fn;
}
registerOnTouched(fn: any): void {
this._onTouched=fn;
}
setDisabledState?(isDisabled: boolean): void {
}
to(v: number,back=false, event?: Event) {
if (event) {
event.preventDefault();
}
if (v <= this.totalPage && v != this._current && v > 0) {
this._current = v;
this._pageInfo=this.getPageInfo();
this.writeValue(v);
this._onChange(this._current);
this.onPageChanged.emit(v);
}else{
back?this.currentEle.nativeElement.value = this._current:void 0;
}
}
}
使用方式如下:
<upc-pager [pageSize]="pageSize" [showInfo]="true" [(ngModel)]="current" [totalItem]="total" (onPageChanged)='onPageChanged($event)'></upc-pager>
效果圖如下:
完整代碼:https://github.com/upcyoung/upc-pagination/tree/master/src/pager