FormControls, whether template-driven or reactive, subscribe for values and write values using Directives that implement ControlValueAccessor
. One important method to check out is selectValueAccessor, which is utilized in all necessary directives. Basic input controls like <input type="text">
and textareas are managed by the DefaultValueAccessor. Another example is the CheckboxValueAccessor used for checkboxes.
The task ahead is quite simple. We just need to create a new value accessor specifically for date input controls.
Let's call it DateValueAccessor
:
// date-value-accessor.ts
import { Directive, ElementRef, HostListener, Renderer, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
export const DATE_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DateValueAccessor),
multi: true
};
/**
* The accessor for writing a value and listening to changes on a date input element
*
* ### Example
* `<input type="date" name="myBirthday" ngModel useValueAsDate>`
*/
@Directive({
selector: '[useValueAsDate]',
providers: [DATE_VALUE_ACCESSOR]
})
export class DateValueAccessor implements ControlValueAccessor {
@HostListener('input', ['$event.target.valueAsDate']) onChange = (_: any) => { };
@HostListener('blur', []) onTouched = () => { };
constructor(private _renderer: Renderer, private _elementRef: ElementRef) { }
writeValue(value: Date): void {
this._renderer.setElementProperty(this._elementRef.nativeElement, 'valueAsDate', value);
}
registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
setDisabledState(isDisabled: boolean): void {
this._renderer.setElementProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
}
}
We link the DateValueAccessor
with the multi-provider DATE_VALUE_ACCESSOR
so that selectValueAccessor can discover it.
The only question remaining is what selector to use. I've opted for an opt-in approach.
Here, the DateValueAccessor is identified by the attribute "useValueAsDate".
<input type="date" name="myBirthday" ngModel useValueAsDate>
OR
<input type="date" name="myBirthday" [(ngModel)]="myBirthday" useValueAsDate>
OR
<input type="date" formControlName="myBirthday" useValueAsDate>
It's also feasible to enhance the default implementation.
The following selector would activate the feature automatically.
// This selector silently modifies previous behavior and could disrupt existing code
selector: 'input[type=date][formControlName],input[type=date][formControl],input[type=date][ngModel]'
However, caution is advised as this change might break existing implementations dependent on old behavior. Therefore, opting for the opt-in version is recommended!
Available on NPM and Github
For user convenience, I've created the project angular-data-value-accessor
on Github.
There's also an NPM package ready:
npm install --save angular-date-value-accessor
Simply import the module through NgModule:
// app.module.ts
import { DateValueAccessorModule } from 'angular-date-value-accessor';
@NgModule({
imports: [
DateValueAccessorModule
]
})
export class AppModule { }
Now you can apply "useValueAsDate" to your date input controls.
Demonstration
To see it in action, check out the demo at: