我想构建一个指令,它可以改变传入和传入输入的值,并与ngModel绑定。
假设我想做一个日期变异,每次模型改变时,mutator首先将值更改为正确的格式(例如" 2017-05-03 00:00:00"是在ngModel更新视图之前显示为" 2017/05/03")。当视图发生变化时,mutator会在ngModel更新模型之前更改值(例如,输入" 2017/08/03"将模型设置为" 2017-08-03 00:00:00 && #34; [时间戳])。
该指令将如下使用:
<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类很复杂,我不知道我在做什么。有什么想法吗?
更新
根据下面的答案,我提出了解决方案:see gist
用法(需要Moment JS):
<input mutate="YYYY/MM/DD" inputFormat="YYYY-MM-DD HH:mm:ss" [(ngModel)]="someDate">
答案 0 :(得分:1)
简短回答:您需要在某个类中实现ControlValueAccessor,并使用某个指令为ngModel提供NG_VALUE_ACCESSOR。这个ControlValueAccessor和指令实际上可以是同一个类。
TL; DR 它不是很明显,但仍然不是很复杂。下面是我的一个日期控件的骨架。这个东西充当角1 ng模型的解析器/格式化器对。
这一切都始于ngModel将所有NG_VALUE_ACCESSOR注入其自身。还有许多默认提供程序,它们都被注入到ngModel构造函数中,但是ngModel可以区分默认值访问器和用户提供的访问器。所以它选择一个合作。粗略地看起来像这样:如果有用户的值访问器,那么它将被选中,否则它将回退到从默认值中选择。完成初始设置后。
控制值访问器应该在输入元素上订阅'input'或其他类似的事件来处理来自它的输入事件。
当外部更改值时,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"/>
答案 1 :(得分: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绑定一起使用。