2017-02-09 7 views
1

Я хотел бы создать директиву, которая может мутировать значения, передаваемые на вход и связанные с ним, связанные с ngModel.Angular 2 ngModel mutator директива

Скажем, я хотел сделать мутацию даты, каждый раз, когда модель изменяется, мутатор сначала получает значение для правильного формата (например, «2017-05-03 00:00:00» отображается как «2017/05/03 "), прежде чем ngModel обновит представление. Когда представление изменяется, мутатор получает изменение значения перед обновлением модели ngModel (например, ввод «2017/08/03» устанавливает модель в «2017-08-03 00:00:00» [timestamp]).

директива будет использоваться, как это:

<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

Использование (требуется Moment JS):

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

ответ

0

Короткий ответ: вам нужно реализовать ControlValueAccessor в некотором классе и предоставить его как NG_VALUE_ACCESSOR для ngModel с некоторой директивой. Этот ControlValueAccessor и директива могут фактически быть одним и тем же классом.

TL; DR Это не очень очевидно, но все еще не очень сложно. Ниже представлен скелет из одного из моих элементов управления датами. Эта вещь действует как пара парсера/форматирования для угловой 1-й модели.

Все начинается с ngModel, впрыскивающего все NG_VALUE_ACCESSOR в себя. Также существует множество поставщиков по умолчанию, и все они вводятся в конструктор ngModel, но ngModel может различать между доступными по умолчанию значениями и теми, которые предоставляются пользователем. Поэтому он выбирает один для работы. Примерно это выглядит так: если есть пользовательский атрибут ценности, тогда он будет выбран, в противном случае он возвращается к выбору по умолчанию. После этого начальная настройка будет выполнена.

Атрибут элемента управления должен подписываться на «ввод» или какое-либо другое подобное событие на элементе ввода для обработки входных событий из него.

Когда значение изменено извне ngModel вызывает метод writeValue() для значения accessor, выбранного при инициализации. Этот метод отвечает за отображение отображаемого значения, которое войдет во ввод в качестве строки, показанной пользователю.

В какой-то момент (обычно на размытие) управление может быть отмечено как затронутое. Это также показано.

Обратите внимание: код ниже не является настоящим производственным кодом, он не был протестирован, он может содержать некоторые несоответствия или неточности, но в целом он показывает всю идею этого подхода.

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.

Смежные вопросы