2017-02-09 94 views
1

我想要構建一個指令,可以改變傳入和傳出的值,並使用ngModel綁定一個輸入。假設我想做一個日期變異,每次模型變化時,變形器首先會將值更改爲適當的格式(例如「2017-05-03 00:00:00」顯示爲「2017/05/03「)之前,ngModel更新視圖。視圖更改時,mutator會在ngModel更新模型之前更改該值(例如,輸入「2017/08/03」將模型設置爲「2017-08-03 00:00:00」[timestamp])。Angular 2 ngModel mutator指令

該指令將用於這樣的:

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

我的第一反應是讓主機組件上的參考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類很複雜,我不知道我在做什麼。有任何想法嗎?

UPDATE

基於答案下面我想出瞭解決方案:see gist

使用(需要時刻JS):

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

回答

0

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

TL; DR 這不是很明顯,但仍不是很複雜。以下是我的一個日期控件的框架。這個東西充當角1 ng模型的解析器/格式器對。

這一切都始於ngModel將所有NG_VALUE_ACCESSOR注入其本身。還有一堆默認提供程序,它們都被注入到ngModel構造函數中,但ngModel可以區分默認值訪問器和用戶提供的默認值訪問器。所以它選擇一個與之合作。大致看起來像這樣:如果有用戶的價值訪問者,那麼它將被挑選出來,否則它會退回到默認選擇。之後,完成初始設置。

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

當外部更改值時,ngModel在初始化期間拾取的value訪問器上調用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"/> 
+0

感謝。它工作的一種享受。我希望不必處理特定的輸入元素('event.target.value'),以便它可以與ngModel支持的任何類型的輸入兼容。也許可以通過獲取現有的默認ControlValueAccessor並在提供給NgModel之前對其進行封裝。基於你的代碼,我提出了這個變異日期,[GIST](https://gist.github.com/christiaan-lombard/31c5e3ccbd55f9ce523d64f9bf48b5f5) – christiaan

+0

你不能那樣做。如果你看看ng2的源代碼,你會發現它們做的是相同的事情 - 它們對於不同類型的輸入只有一堆價值訪問器。 –

0

你不應該做任何事情與形式在這裏。舉個例子,我做了一個信用卡掩碼指令,將用戶輸入格式化爲信用卡字符串(基本上每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綁定。