@Directive({ // tslint:disable-next-line:directive-selector selector: 'input[type=datetime][valueAsDate]', providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DateValueDirective), multi: true } ] }) export class DateValueDirective implements ControlValueAccessor { /** * See https://date-fns.org/v2.0.0-alpha.25/docs/format * 自定义日期展示格式 * @type {string} * @memberof DateValueDirective */ // tslint:disable-next-line:no-input-rename @Input('valueAsDate') format: string; private dateValue: Date; @HostListener('input', ['$event.target.value']) onChange = (_: any) => { }; @HostListener('blur', []) onTouched = () => { }; get element() { return this.elementRef.nativeElement; } constructor( private elementRef: ElementRef, private renderer: Renderer2 // <1> ) { } parseDate(str: string) { return parseDate(str, this.format, new Date(), { awareOfUnicodeTokens: true }); } formatDate(date: Date) { return formatDate(date, this.format, { awareOfUnicodeTokens: true }); } /** * 设置组件的值的时候,先把新的值存到一个成员变量中,然后再把新的值格式化为 string */ writeValue(date: Date): void { this.dateValue = date; this.renderer.setProperty(this.element, 'value', this.formatDate(date)); } /** * 在 input 元素值发生变化的时候,先尝试把变化后的值转换成 Date 对象 * 如果转换失败,那么依然使用之前的值 * 否则,将新的值传递给回调函数 */ registerOnChange(fn: any): void { const onChange = (value: string) => { const date = this.parseDate(value); if (isValidDate(date)) { this.dateValue = date; fn(date); } else { fn(this.dateValue); } }; this.onChange = onChange; } registerOnTouched(fn: any): void { this.onTouched = fn; } setDisabledState?(isDisabled: boolean): void { this.renderer.setProperty(this.element, 'disabled', isDisabled); } }
这里演示了使用 Renderer2 来读写元素属性的操作
整个指令的内容仍然非常简单,但是却能够为我们的日常开发带来不小的便利,使用了这个指令后,我们就可以非常容易的为 Date 对象进行双向绑定。
<input type="datetime" valueAsDate="M/d/yyyy h:mm:ss a" [(ngModel)]="foo.date">