Javascript Angular 2 ngModel mutator指令

Javascript Angular 2 ngModel mutator指令,javascript,angular,angular2-forms,Javascript,Angular,Angular2 Forms,我想构建一个指令,该指令可以改变与ngModel绑定的输入之间传递的值 假设我想做一个日期变异,每次模型改变时,变异者首先将值更改为正确的格式(例如“2017-05-03 00:00:00”显示为“2017/05/03”),然后ngModel更新视图。当视图更改时,在ngModel更新模型之前,mutator会更改值(例如输入“2017/08/03”将模型设置为“2017-08-03 00:00:00”[时间戳]) 该指令的使用方式如下: <input [(ngModel)]="someM

我想构建一个指令,该指令可以改变与ngModel绑定的输入之间传递的值

假设我想做一个日期变异,每次模型改变时,变异者首先将值更改为正确的格式(例如“2017-05-03 00:00:00”显示为“2017/05/03”),然后ngModel更新视图。当视图更改时,在ngModel更新模型之前,mutator会更改值(例如输入“2017/08/03”将模型设置为“2017-08-03 00:00:00”[时间戳])

该指令的使用方式如下:

<input [(ngModel)]="someModel" mutate="date:YYYY/MM/DD" />
然后我意识到Angular 2 Forms类很复杂,我不知道我在做什么。有什么想法吗

更新

根据下面的答案,我提出了解决方案:

用法(需要时间):


您不应该在这里对表单做任何操作。例如,我制作了一个信用卡屏蔽指令,将用户输入格式化为信用卡字符串(基本上每4个字符一个空格)

import{Directive,ElementRef,HostListener,Input}来自“@angular/core”;
@指示({
选择器:“[信用卡]”//属性选择器
})
出口信用卡{
@HostListener('输入',['$event']))
confirmFirst(事件:任何){
设val=event.target.value;
event.target.value=此.setElement(val);
}
构造函数(公共元素:ElementRef){}
setElement(val){
让num='';
var v=val.replace(/\s+/g')。replace(/[^0-9]/gi');
var matches=v.match(/\d{4,16}/g);
var match=匹配项和匹配项[0]| |“”;
var部分=[];
对于(变量i=0,len=match.length;i
然后我在模板中使用它,如下所示:

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


在本例中,我使用表单控件,但这两种方式都不重要。它应该可以与ngModel绑定一起工作。

简短回答:您需要在某个类中实现ControlValueAccessor,并将其作为ngModel的NG_值访问器提供,并带有一些指令。这个ControlValueAccessor和指令实际上可以是同一个类

TL;博士 这不是很明显,但也不是很复杂。下面是我的一个日期控件的骨架。它充当angular 1 ng模型的解析器/格式化程序对

这一切都始于ngModel将所有NG_值访问器注入自身。也有很多默认的提供者,它们都被注入到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"/>


谢谢您的帮助。这是一种享受。我希望不必处理特定的输入元素(
event.target.value
),这样它就可以与ngModel支持的任何类型的输入兼容。可能通过抓取现有的默认ControlValueAccessor并在将其提供给NgModel之前将其包装。根据你的代码,我为变异日期想出了这个,你不能这么做。如果你看看ng2资源,你会发现它们做了同样的事情——它们只是有一堆不同类型输入的值访问器。
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);
    }

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