2017-02-09 95 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绑定。