国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

[譯] 別再對 Angular 表單的 ControlValueAccessor 感到迷惑

blastz / 3500人閱讀

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

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

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

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

FormControl 和 ControlValueAccessor

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

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

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

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

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

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

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

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

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

再次強(qiáng)調(diào),不管是使用響應(yīng)式表單顯式創(chuàng)建還是使用模板驅(qū)動表單隱式創(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 原生控件時,會使用DefaultValueAccessor 指令:

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

所有表單指令,包括上面代碼中的 formControl 指令,都會調(diào)用 setUpControl 函數(shù)來讓表單控件和DefaultValueAccessor 實現(xiàn)交互(譯者注:意思就是上面代碼中綁定的 formControl 指令,在其自身實例化時,會調(diào)用 setUpControl() 函數(shù)給同樣綁定到 input DefaultValueAccessor 指令做好安裝工作,如 L85,這樣 formControl 指令就可以借助 DefaultValueAccessor 來和 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 有了點小小變動,但基本相似):

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è)置原生控件值更新時監(jiān)聽器,每當(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)聽器,每當(dāng) Angular 表單控件值更新,原生控件值也更新
  control.registerOnChange((newValue: any, ...) => {
    dir.valueAccessor.writeValue(newValue);
  });

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

組件封裝器

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

簡單的封裝器

最基礎(chǔ)實現(xiàn)是通過簡單封裝使其能在屏幕上顯示出來,所以我們需要一個 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)建一個 slider 控件,然后使用 widget 屬性引用這個控件。

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

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

Hello {{name}}

` }) export class AppComponent { ... }

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



這里是安裝依賴的源碼

交互式表單控件

上面的實現(xiàn)還不能讓我們自定義的 slider 控件與父組件交互,所以還得使用輸入/輸出綁定來是實現(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 生命周期鉤子來追蹤輸入屬性 value 值的變化,一旦其值變化,我們就將該值設(shè)置為 slider 控件的值。

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


源碼在這里。

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

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

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

注冊 NG_VALUE_ACCESSOR 提供者

實現(xiàn) ControlValueAccessor 接口

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

讓我們首先定義提供者:

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

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

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)實現(xiàn)自定義 controlValueAccessor,我建議還是放在類裝飾器里吧(譯者注:個人建議還是學(xué)習(xí) Angular 源碼那樣放在外面)。

一旦定義了提供者后,就讓我們實現(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) {  }

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

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

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

現(xiàn)在,實現(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}); } }

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

Github

項目的 Github 倉庫

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

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/93895.html

相關(guān)文章

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

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

    yangrd 評論0 收藏0
  • [] 再對 Angular Modules 感到迷惑

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

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

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

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

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

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

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

    pekonchan 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<