亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專(zhuān)欄INFORMATION COLUMN

[譯] 別再對(duì) Angular 表單的 ControlValueAccessor 感到迷惑

blastz / 3573人閱讀

摘要:在里我們簡(jiǎn)單保存了對(duì)回調(diào)函數(shù)的引用,回調(diào)函數(shù)是由指令傳入的譯者注參考,只要每次組件值發(fā)生改變,就會(huì)觸發(fā)這個(gè)回調(diào)函數(shù)。

原文鏈接:Never again be confused when implementing ControlValueAccessor in Angular?forms

如果你正在做一個(gè)復(fù)雜項(xiàng)目,必然會(huì)需要自定義表單控件,這個(gè)控件主要需要實(shí)現(xiàn) ControlValueAccessor 接口(譯者注:該接口定義方法可參考 API 文檔說(shuō)明,也可參考 Angular 源碼定義)。網(wǎng)上有大量文章描述如何實(shí)現(xiàn)這個(gè)接口,但很少說(shuō)到它在 Angular 表單架構(gòu)里扮演什么角色,如果你不僅僅想知道如何實(shí)現(xiàn),還想知道為什么這樣實(shí)現(xiàn),那本文正合你的胃口。

首先我解釋下為啥需要 ControlValueAccessor 接口以及它在 Angular 中是如何使用的。然后我將展示如何封裝第三方組件作為 Angular 組件,以及如何使用輸入輸出機(jī)制實(shí)現(xiàn)組件間通信(譯者注:Angular 組件間通信輸入輸出機(jī)制可參考官網(wǎng)文檔),最后將展示如何使用 ControlValueAccessor 來(lái)實(shí)現(xiàn)一種針對(duì) Angular 表單新的數(shù)據(jù)通信機(jī)制。

FormControl 和 ControlValueAccessor

如果你之前使用過(guò) Angular 表單,你可能會(huì)熟悉 FormControl ,Angular 官方文檔將它描述為追蹤單個(gè)表單控件值和有效性的實(shí)體對(duì)象。需要明白,不管你使用模板驅(qū)動(dòng)還是響應(yīng)式表單(譯者注:即模型驅(qū)動(dòng)),FormControl 都總會(huì)被創(chuàng)建。如果你使用響應(yīng)式表單,你需要顯式創(chuàng)建 FormControl 對(duì)象,并使用 formControlformControlName 指令來(lái)綁定原生控件;如果你使用模板驅(qū)動(dòng)方法,FormControl 對(duì)象會(huì)被 NgModel 指令隱式創(chuàng)建(譯者注:可查看 Angular 源碼這一行):

@Directive({
  selector: "[ngModel]...",
  ...
})
export class NgModel ... {
  _control = new FormControl();   <---------------- here

不管 formControl 是隱式還是顯式創(chuàng)建,都必須和原生 DOM 表單控件如 input,textarea 進(jìn)行交互,并且很有可能需要自定義一個(gè)表單控件作為 Angular 組件而不是使用原生表單控件,而通常自定義表單控件會(huì)封裝一個(gè)使用純 JS 寫(xiě)的控件如 jQuery UI"s Slider。本文我將使用原生表單控件術(shù)語(yǔ)來(lái)區(qū)分 Angular 特定的 formControl 和你在 html 使用的表單控件,但你需要知道任何一個(gè)自定義表單控件都可以和 formControl 指令進(jìn)行交互,而不是原生表單控件如 input。

原生表單控件數(shù)量是有限的,但是自定義表單控件是無(wú)限的,所以 Angular 需要一種通用機(jī)制來(lái)橋接原生/自定義表單控件和 formControl 指令,而這正是 ControlValueAccessor 干的事情。這個(gè)對(duì)象橋接原生表單控件和 formControl 指令,并同步兩者的值。官方文檔是這么描述的(譯者注:為清晰理解,該描述不翻譯):

?ControlValueAccessor?acts as a bridge between the Angular forms API and a native element in the DOM.

任何一個(gè)組件或指令都可以通過(guò)實(shí)現(xiàn) ControlValueAccessor 接口并注冊(cè)為 NG_VALUE_ACCESSOR,從而轉(zhuǎn)變成 ControlValueAccessor 類(lèi)型的對(duì)象,稍后我們將一起看看如何做。另外,這個(gè)接口還定義兩個(gè)重要方法——writeValueregisterOnChange (譯者注:可查看 Angular 源碼這一行):

interface ControlValueAccessor {
  writeValue(obj: any): void
  registerOnChange(fn: any): void
  registerOnTouched(fn: any): void
  ...
}

formControl 指令使用 writeValue 方法設(shè)置原生表單控件的值(譯者注:你可能會(huì)參考 L186L41);使用 registerOnChange 方法來(lái)注冊(cè)由每次原生表單控件值更新時(shí)觸發(fā)的回調(diào)函數(shù)(譯者注:你可能會(huì)參考這三行,L186L43,以及 L85),你需要把更新的值傳給這個(gè)回調(diào)函數(shù),這樣對(duì)應(yīng)的 Angular 表單控件值也會(huì)更新(譯者注:這一點(diǎn)可以參考 Angular 它自己寫(xiě)的 DefaultValueAccessor 的寫(xiě)法是如何把 input 控件每次更新值傳給回調(diào)函數(shù)的,L52L89);使用 registerOnTouched 方法來(lái)注冊(cè)用戶和控件交互時(shí)觸發(fā)的回調(diào)(譯者注:你可能會(huì)參考 L95)。

下圖是 Angular 表單控件 如何通過(guò) ControlValueAccessor 來(lái)和原生表單控件交互的(譯者注:formControl你寫(xiě)的或者 Angular 提供的 CustomControlValueAccessor 兩個(gè)都是要綁定到 native DOM element 的指令,而 formControl 指令需要借助 CustomControlValueAccessor 指令/組件,來(lái)和 native DOM element 交換數(shù)據(jù)。):

再次強(qiáng)調(diào),不管是使用響應(yīng)式表單顯式創(chuàng)建還是使用模板驅(qū)動(dòng)表單隱式創(chuàng)建,ControlValueAccessor 都總是和 Angular 表單控件進(jìn)行交互。

Angular 也為所有原生 DOM 表單元素創(chuàng)建了 Angular 表單控件(譯者注:Angular 內(nèi)置的 ControlValueAccessor):

Accessor Form Element
DefaultValueAccessor input,textarea
CheckboxControlValueAccessor input[type=checkbox]
NumberValueAccessor input[type=number]
RadioControlValueAccessor input[type=radio]
RangeValueAccessor input[type=range]
SelectControlValueAccessor select
SelectMultipleControlValueAccessor select[multiple]

從上表中可看到,當(dāng) Angular 在組件模板中中遇到 inputtextarea DOM 原生控件時(shí),會(huì)使用DefaultValueAccessor 指令:

@Component({
  selector: "my-app",
  template: `
      
  `
})
export class AppComponent {
  ctrl = new FormControl(3);
}

所有表單指令,包括上面代碼中的 formControl 指令,都會(huì)調(diào)用 setUpControl 函數(shù)來(lái)讓表單控件和DefaultValueAccessor 實(shí)現(xiàn)交互(譯者注:意思就是上面代碼中綁定的 formControl 指令,在其自身實(shí)例化時(shí),會(huì)調(diào)用 setUpControl() 函數(shù)給同樣綁定到 input DefaultValueAccessor 指令做好安裝工作,如 L85,這樣 formControl 指令就可以借助 DefaultValueAccessor 來(lái)和 input 元素交換數(shù)據(jù)了)。細(xì)節(jié)可參考 formControl 指令的代碼:

export class FormControlDirective ... {
  ...
  ngOnChanges(changes: SimpleChanges): void {
    if (this._isControlChanged(changes)) {
      setUpControl(this.form, this);

還有 setUpControl 函數(shù)源碼也指出了原生表單控件和 Angular 表單控件是如何數(shù)據(jù)同步的(譯者注:作者貼的可能是 Angular v4.x 的代碼,v5 有了點(diǎn)小小變動(dòng),但基本相似):

export function setUpControl(control: FormControl, dir: NgControl) {
  
  // initialize a form control
  // 調(diào)用 writeValue() 初始化表單控件值
  dir.valueAccessor.writeValue(control.value);
  
  // setup a listener for changes on the native control
  // and set this value to form control
  // 設(shè)置原生控件值更新時(shí)監(jiān)聽(tīng)器,每當(dāng)原生控件值更新,Angular 表單控件值也更新
  valueAccessor.registerOnChange((newValue: any) => {
    control.setValue(newValue, {emitModelToViewChange: false});
  });

  // setup a listener for changes on the Angular formControl
  // and set this value to the native control
  // 設(shè)置 Angular 表單控件值更新監(jiān)聽(tīng)器,每當(dāng) Angular 表單控件值更新,原生控件值也更新
  control.registerOnChange((newValue: any, ...) => {
    dir.valueAccessor.writeValue(newValue);
  });

只要我們理解了內(nèi)部機(jī)制,就可以實(shí)現(xiàn)我們自定義的 Angular 表單控件了。

組件封裝器

由于 Angular 為所有默認(rèn)原生控件提供了控件值訪問(wèn)器,所以在封裝第三方插件或組件時(shí),需要寫(xiě)一個(gè)新的控件值訪問(wèn)器。我們將使用上文提到的 jQuery UI 庫(kù)的 slider 插件,來(lái)實(shí)現(xiàn)一個(gè)自定義表單控件吧。

簡(jiǎn)單的封裝器

最基礎(chǔ)實(shí)現(xiàn)是通過(guò)簡(jiǎn)單封裝使其能在屏幕上顯示出來(lái),所以我們需要一個(gè) NgxJquerySliderComponent 組件,并在其模板里渲染出 slider

@Component({
  selector: "ngx-jquery-slider",
  template: `
      
`, styles: ["div {width: 100px}"] }) export class NgxJquerySliderComponent { @ViewChild("location") location; widget; ngOnInit() { this.widget = $(this.location.nativeElement).slider(); } }

這里我們使用標(biāo)準(zhǔn)的 jQuery 方法在原生 DOM 元素上創(chuàng)建一個(gè) slider 控件,然后使用 widget 屬性引用這個(gè)控件。

一旦簡(jiǎn)單封裝好了 slider 組件,我們就可以在父組件模板里使用它:

@Component({
  selector: "my-app",
  template: `
      

Hello {{name}}

` }) export class AppComponent { ... }

為了運(yùn)行程序我們需要加入 jQuery 相關(guān)依賴,簡(jiǎn)化起見(jiàn),在 index.html 中添加全局依賴:



這里是安裝依賴的源碼

交互式表單控件

上面的實(shí)現(xiàn)還不能讓我們自定義的 slider 控件與父組件交互,所以還得使用輸入/輸出綁定來(lái)是實(shí)現(xiàn)組件間數(shù)據(jù)通信:

export class NgxJquerySliderComponent {
  @ViewChild("location") location;
  @Input() value;
  @Output() private valueChange = new EventEmitter();
  widget;

  ngOnInit() {
    this.widget = $(this.location.nativeElement).slider();   
    this.widget.slider("value", this.value);
    this.widget.on("slidestop", (event, ui) => {
      this.valueChange.emit(ui.value);
    });
  }

  ngOnChanges() {
    if (this.widget && this.widget.slider("value") !== this.value) {
      this.widget.slider("value", this.value);
    }
  }
}

一旦 slider 組件創(chuàng)建,就可以訂閱 slidestop 事件獲取變化的值,一旦 slidestop 事件被觸發(fā)了,就可以使用輸出事件發(fā)射器 valueChanges 通知父組件。當(dāng)然我們也可以使用 ngOnChanges 生命周期鉤子來(lái)追蹤輸入屬性 value 值的變化,一旦其值變化,我們就將該值設(shè)置為 slider 控件的值。

然后就是父組件中如何使用 slider 組件的代碼實(shí)現(xiàn):


源碼在這里。

但是,我們想要的是,使用 slider 組件作為表單的一部分,并使用模板驅(qū)動(dòng)表單或響應(yīng)式表單的指令與其數(shù)據(jù)通信,那就需要讓其實(shí)現(xiàn) ControlValueAccessor 接口了。由于我們將實(shí)現(xiàn)的是新的組件通信方式,所以不需要標(biāo)準(zhǔn)的輸入輸出屬性綁定方式,那就移除相關(guān)代碼吧。(譯者注:作者先實(shí)現(xiàn)標(biāo)準(zhǔn)的輸入輸出屬性綁定的通信方式,又要?jiǎng)h除,主要是為了引入新的表單組件交互方式,即 ControlValueAccessor。)

實(shí)現(xiàn)自定義控件值訪問(wèn)器

實(shí)現(xiàn)自定義控件值訪問(wèn)器并不難,只需要兩步:

注冊(cè) NG_VALUE_ACCESSOR 提供者

實(shí)現(xiàn) ControlValueAccessor 接口

NG_VALUE_ACCESSOR 提供者用來(lái)指定實(shí)現(xiàn)了 ControlValueAccessor 接口的類(lèi),并且被 Angular 用來(lái)和 formControl 同步,通常是使用組件類(lèi)或指令來(lái)注冊(cè)。所有表單指令都是使用NG_VALUE_ACCESSOR 標(biāo)識(shí)來(lái)注入控件值訪問(wèn)器,然后選擇合適的訪問(wèn)器(譯者注:這句話可參考這兩行代碼,L175L181)。要么選擇DefaultValueAccessor 或者內(nèi)置的數(shù)據(jù)訪問(wèn)器,否則 Angular 將會(huì)選擇自定義的數(shù)據(jù)訪問(wèn)器,并且有且只有一個(gè)自定義的數(shù)據(jù)訪問(wèn)器(譯者注:這句話參考 selectValueAccessor 源碼實(shí)現(xiàn))。

讓我們首先定義提供者:

@Component({
  selector: "ngx-jquery-slider",
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: NgxJquerySliderComponent,
    multi: true
  }]
  ...
})
class NgxJquerySliderComponent implements ControlValueAccessor {...}

我們直接在組件裝飾器里直接指定類(lèi)名,然而 Angular 源碼默認(rèn)實(shí)現(xiàn)是放在類(lèi)裝飾器外面:

export const DEFAULT_VALUE_ACCESSOR: any = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => DefaultValueAccessor),
  multi: true
};
@Directive({
  selector:"input",
  providers: [DEFAULT_VALUE_ACCESSOR]
  ...
})
export class DefaultValueAccessor implements ControlValueAccessor {}

放在外面就需要使用 forwardRef,關(guān)于原因可以參考 What is forwardRef in Angular and why we need it 。當(dāng)實(shí)現(xiàn)自定義 controlValueAccessor,我建議還是放在類(lèi)裝飾器里吧(譯者注:個(gè)人建議還是學(xué)習(xí) Angular 源碼那樣放在外面)。

一旦定義了提供者后,就讓我們實(shí)現(xiàn) controlValueAccessor 接口:

export class NgxJquerySliderComponent implements ControlValueAccessor {
  @ViewChild("location") location;
  widget;
  onChange;
  value;
  
ngOnInit() {
    this.widget = $(this.location.nativeElement).slider(this.value);
   this.widget.on("slidestop", (event, ui) => {
      this.onChange(ui.value);
    });
}
  
writeValue(value) {
    this.value = value;
    if (this.widget && value) {
      this.widget.slider("value", value);
    }
  }
  
registerOnChange(fn) { this.onChange = fn;  }

registerOnTouched(fn) {  }

由于我們對(duì)用戶是否與組件交互不感興趣,所以先把 registerOnTouched 置空吧。在registerOnChange 里我們簡(jiǎn)單保存了對(duì)回調(diào)函數(shù) fn 的引用,回調(diào)函數(shù)是由 formControl 指令傳入的(譯者注:參考 L85),只要每次 slider 組件值發(fā)生改變,就會(huì)觸發(fā)這個(gè)回調(diào)函數(shù)。在 writeValue 方法內(nèi)我們把得到的值傳給 slider 組件。

現(xiàn)在我們把上面描述的功能做成一張交互式圖:

如果你把簡(jiǎn)單封裝和 controlValueAccessor 封裝進(jìn)行比較,你會(huì)發(fā)現(xiàn)父子組件交互方式是不一樣的,盡管封裝的組件與 slider 組件的交互是一樣的。你可能注意到 formControl 指令實(shí)際上簡(jiǎn)化了與父組件交互的方式。這里我們使用 writeValue 來(lái)向子組件寫(xiě)入數(shù)據(jù),而在簡(jiǎn)單封裝方法中使用 ngOnChanges;調(diào)用 this.onChange 方法輸出數(shù)據(jù),而在簡(jiǎn)單封裝方法中使用 this.valueChange.emit(ui.value)。

現(xiàn)在,實(shí)現(xiàn)了 ControlValueAccessor 接口的自定義 slider 表單控件完整代碼如下:

@Component({
  selector: "my-app",
  template: `
      

Hello {{name}}

Current slider value: {{ctrl.value}} ` }) export class AppComponent { ctrl = new FormControl(11); updateSlider($event) { this.ctrl.setValue($event.currentTarget.value, {emitModelToViewChange: true}); } }

你可以查看程序的最終實(shí)現(xiàn)

Github

項(xiàng)目的 Github 倉(cāng)庫(kù)。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/93895.html

相關(guān)文章

  • @angular/forms 源碼解析之雙向綁定

    摘要:由于的屬性提供了令牌,并且該令牌指向的對(duì)象就是,所以構(gòu)造函數(shù)中注入的令牌包含的對(duì)象數(shù)組只有一個(gè)。這樣的構(gòu)造函數(shù)里就會(huì)包含一個(gè)對(duì)象,然后把這個(gè)傳給對(duì)象,最后注冊(cè)回調(diào),這樣以后值更新時(shí)就會(huì)運(yùn)行。整個(gè)包的設(shè)計(jì)也是按照這種數(shù)據(jù)流形式,并不復(fù)雜。 我們知道,Angular 的 @angular/forms 包提供了 NgModel 指令,來(lái)實(shí)現(xiàn)雙向綁定,即把一個(gè) JS 變量(假設(shè)為 name)與...

    yangrd 評(píng)論0 收藏0
  • [] 再對(duì) Angular Modules 感到迷惑

    摘要:大多數(shù)初學(xué)者會(huì)認(rèn)為也有封裝規(guī)則,但實(shí)際上沒(méi)有。第二個(gè)規(guī)則是最后導(dǎo)入模塊的,會(huì)覆蓋前面導(dǎo)入模塊的。 原文鏈接:Avoiding common confusions with modules in Angular showImg(https://segmentfault.com/img/remote/1460000015298243?w=270&h=360); Angular Modul...

    LMou 評(píng)論0 收藏0
  • Angular 使用 ControlValueAccessor 創(chuàng)建自定義表單控件

    摘要:在自定義表單控件,有時(shí)你想要的輸入不是標(biāo)準(zhǔn)的文本輸入選擇或復(fù)選框。它獲取一個(gè)函數(shù),告訴其他表單指令和表單控件更新其值。與此類(lèi)似,它專(zhuān)門(mén)為控件接收觸摸事件時(shí)注冊(cè)一個(gè)處理程序。 在 Angular 自定義表單控件,有時(shí)你想要的輸入不是標(biāo)準(zhǔn)的文本輸入、選擇或復(fù)選框。通過(guò)實(shí)現(xiàn)ControlValueAccessor 接口并將組件注冊(cè)為 NG_VALUE_ACCESSOR,您可以將自定義表單控件...

    AJie 評(píng)論0 收藏0
  • 構(gòu)建一個(gè)自定義 angular2 輸入組件

    摘要:構(gòu)建一個(gè)自定義輸入組件今天我們來(lái)學(xué)習(xí)如何正確的構(gòu)建和一個(gè)具有和同樣作用,但同時(shí)也具有自己的邏輯的輸入組件。值訪問(wèn)器在完成上面的一些步驟之后,我們的組件基本功能完成了,但是接下來(lái)還有最重要的一部分內(nèi)容,那就是讓我們的自定義組件獲得值訪問(wèn)權(quán)限。 構(gòu)建一個(gè)自定義 angular2 輸入組件 今天我們來(lái)學(xué)習(xí)如何正確的構(gòu)建和一個(gè)具有和 同樣作用,但同時(shí)也具有自己的邏輯的輸入組件。 在讀這篇文章...

    CNZPH 評(píng)論0 收藏0
  • 構(gòu)建一個(gè)自定義 angular2 輸入組件

    摘要:構(gòu)建一個(gè)自定義輸入組件今天我們來(lái)學(xué)習(xí)如何正確的構(gòu)建和一個(gè)具有和同樣作用,但同時(shí)也具有自己的邏輯的輸入組件。值訪問(wèn)器在完成上面的一些步驟之后,我們的組件基本功能完成了,但是接下來(lái)還有最重要的一部分內(nèi)容,那就是讓我們的自定義組件獲得值訪問(wèn)權(quán)限。 構(gòu)建一個(gè)自定義 angular2 輸入組件 今天我們來(lái)學(xué)習(xí)如何正確的構(gòu)建和一個(gè)具有和 同樣作用,但同時(shí)也具有自己的邏輯的輸入組件。 在讀這篇文章...

    pekonchan 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<