簡體   English   中英

Angular 2 ngModel mutator 指令

[英]Angular 2 ngModel mutator directive

我想構建一個指令,該指令可以改變傳入和傳出輸入的值,並與 ngModel 綁定。

假設我想做一個日期突變,每次模型更改時,mutator 首先將值更改為正確的格式(例如“2017-05-03 00:00:00”顯示為“2017/05/03 "),在 ngModel 更新視圖之前。 當視圖發生變化時,mutator 會在 ngModel 更新模型之前更改值(例如,輸入“2017/08/03”將模型設置為“2017-08-03 00:00:00”[時間戳])。

該指令將像這樣使用:

<input [(ngModel)]="someModel" mutate="date:YYYY/MM/DD" />

我的第一直覺是獲取對 Host 組件上 ControlValueAccessor 和 NgModel 的引用。

import { Directive, ElementRef, Input, 
         Host, OnChanges, Optional, Self, Inject } from '@angular/core';
import { NgModel, ControlValueAccessor, 
         NG_VALUE_ACCESSOR } from '@angular/forms';


@Directive({ 
    selector: '[mutate]',
})
export class MutateDirective {

    constructor(
        @Host() private _ngModel: NgModel, 
        @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) 
            private _controlValueAccessor: ControlValueAccessor[]
    ){
        console.log('mutute construct', _controlValueAccessor);
    }


}

然后我意識到 Angular 2 Forms 類很復雜,我不知道我在做什么。 有任何想法嗎?

更新

根據下面的答案,我想出了解決方案: 見要點

用法(需要 Moment JS):

<input  mutate="YYYY/MM/DD" inputFormat="YYYY-MM-DD HH:mm:ss" [(ngModel)]="someDate">

簡短回答:您需要在某個類中實現 ControlValueAccessor,並將其作為 NG_VALUE_ACCESSOR 提供給帶有某些指令的 ngModel。 這個 ControlValueAccessor 和指令實際上可以是同一個類。

TL;DR 這不是很明顯,但仍然不是很復雜。 下面是我的一個日期控件的骨架。 這個東西充當 angular 1 ng-model 的解析器/格式化器對。

這一切都始於 ngModel 將所有 NG_VALUE_ACCESSOR 注入自身。 還有一堆默認提供者,它們都被注入到 ngModel 構造函數中,但 ngModel 可以區分默認值訪問器和用戶提供的訪問器。 所以它選擇一個來工作。 粗略地看起來像這樣:如果有用戶的值訪問器,那么它將被選擇,否則它會回到從默認值中進行選擇。 完成初始設置后。

控制值訪問器應該訂閱輸入元素上的“輸入”或其他類似事件,以處理來自它的輸入事件。

當值在外部更改時,ngModel 會在初始化期間選取的值訪問器上調用 writeValue() 方法。 此方法負責呈現將作為字符串顯示給用戶的輸入的顯示值。

在某些時候(通常在模糊事件上)控件可以被標記為已觸摸。 這也顯示出來。

請注意:下面的代碼不是真正的生產代碼,還沒有經過測試,它可能包含一些差異或不准確之處,但總的來說,它顯示了這種方法的整體思路。

import {
    Directive,
    Input,
    Output,
    SimpleChanges,
    ElementRef,
    Renderer,
    EventEmitter,
    OnInit,
    OnDestroy,
    OnChanges,
    forwardRef
} from '@angular/core';
import {Subscription, Observable} from 'rxjs';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';

const DATE_INPUT_VALUE_ACCESSOR_PROVIDER = [
    {provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DateInputDirective), multi: true}
];

@Directive({
    // [date-input] is just to distinguish where exactly to place this control value accessor
    selector: 'input[date-input]',
    providers: [DATE_INPUT_VALUE_ACCESSOR_PROVIDER],
    host: { 'blur': 'onBlur()', 'input': 'onChange($event)' }
})
export class DateInputDirective implements ControlValueAccessor, OnChanges {

    @Input('date-input')
    format: string;

    model: TimeSpan;

    private _onChange: (value: Date) => void = () => {
    };

    private _onTouched: () => void = () => {
    };

    constructor(private _renderer: Renderer,
                private _elementRef: ElementRef,
                // something that knows how to parse value
                private _parser: DateParseTranslator,
                // something that knows how to format it back into string
                private _formatter: DateFormatPipe) {
    }

    ngOnInit() {

    }

    ngOnChanges(changes: SimpleChanges) {
        if (changes['format']) {
            this.updateText(this.model, true);
        }
    }

    onBlur = () => {
        this.updateText(this.model, false);
        this.onTouched();
    };

    onChange = ($event: KeyboardEvent) => {
        // the value of an input - don't remember exactly where it is in the event
        // so this part may be incorrect, please check
        let value = $event.target.value;
        let date = this._parser.translate(value);
        this._onChange(date);
    };

    onTouched = () => {
        this._onTouched();
    };

    registerOnChange = (fn: (value: Date) => void): void => {
        this._onChange = fn;
    };

    registerOnTouched = (fn: () => void): void => {
        this._onTouched = fn;
    };

    writeValue = (value: Date): void => {
        this.model = value;
        this.updateText(value, true);
    };

    updateText = (date: Date, forceUpdate = false) => {
        let textValue = date ? this._formatter.transform(date, this.format) : '';
        if ((!date || !textValue) && !forceUpdate) {
            return;
        }
        this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', textValue);
    }

}

然后在html模板中:

<input date-input="DD/MM/YYYY" [(ngModel)]="myModel"/>

您不應該在這里對 Forms 做任何事情。 例如,我制作了一個信用卡屏蔽指令,將用戶輸入格式化為信用卡字符串(基本上每 4 個字符一個空格)。

import { Directive, ElementRef, HostListener, Input } from '@angular/core';

@Directive({
  selector: '[credit-card]' // Attribute selector
})
export class CreditCard {

  @HostListener('input', ['$event'])
  confirmFirst(event: any) {
    let val = event.target.value;
    event.target.value = this.setElement(val);
  }

  constructor(public element: ElementRef) { }

  setElement(val) {
    let num = '';
    var v = val.replace(/\s+/g, '').replace(/[^0-9]/gi, '');
    var matches = v.match(/\d{4,16}/g);
    var match = matches && matches[0] || '';
    var parts = [];
    for (var i = 0, len = match.length; i < len; i += 4) {
      parts.push(match.substring(i, i + 4));
    }
    if (parts.length) {
      num = parts.join(' ').trim();
    } else {
      num = val.trim();
    }
    return num;
  }

}

然后我在模板中使用它,如下所示:

<input credit-card type="text" formControlName="cardNo" />

我在這個例子中使用了表單控件,但無論哪種方式都沒有關系。 它應該適用於 ngModel 綁定。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM