Cory Rylan

My name is , Google Developer Expert, Speaker, Software Developer. Building Design Systems and Web Components.

Follow @coryrylan
Angular

Using HTML5 Date Input with Date Objects and Angular

Cory Rylan

- 3 minutes

Updated

This article has been updated to the latest version Angular 17 and tested with Angular 16. The content is likely still applicable for all Angular 2 + versions.

Using HTML input elements, we can set the value via the value attribute or property. For date inputs, the value is a string type with the yyyy-dd-mm format.

<input type="date" value="2020-11-30" />
<script>
  const input = document.querySelector('input');
  input.value = '2020-11-30';
</script>

When using Angular forms, we bind to the formControl or ngModel directives to
set the form inputs. However, Angular forms set inputs via the value property.
We have to convert to and from the yyyy-dd-mm date string format if we are working with date objects in our application.

Fortunately, we have a couple of workarounds to make it easier to use Date objects and HTML5 date inputs in Angular.

Using ValueAsDate with HTML5 Datepicker

While the HTML5 datepicker uses a date string for value, it can also set the value with an alternate property called valueAsDate. This allows us to set or get the datepicker value as a JavaScript Date object.

<input type="date" />
<script>
  const input = document.querySelector('input');
  input.valueAsDate = new Date();
</script>

Using Angular and its property/event syntax, we can get and set the valueAsDate property.

<label>Native property/event binding</label>
<input type="date" [valueAsDate]="dateObj" (change)="dateObj = $event.target.valueAsDate" />

This allows our input to use Date objects, but now we no longer use Angular forms losing its benefits. We can work around this limitation by creating a custom Angular Form Control Directive to intercept and map to a Date object or string dynamically.

Form Control Directive

We can enable date objects on our native date inputs by creating an Angular Form Directive that will select an input[type=date] selectors.

import { Directive, ElementRef, HostListener, Renderer2, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Directive({
  selector: 'input[type=date][ngModel], input[type=date][formControl]',
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => DateInputConverter),
    multi: true
  }]
})
export class DateInputConverter implements ControlValueAccessor {

}

This directive will only enable our date object behavior on date inputs using Angular Form inputs.

import { Directive, ElementRef, HostListener, Renderer2, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Directive({
  selector: 'input[type=date][ngModel], input[type=date][formControl]',
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => DateInputConverter),
    multi: true
  }]
})
export class DateInputConverter implements ControlValueAccessor {
  @HostListener('blur', []) onTouched: any = () => { };
  @HostListener('input', ['$event']) onChange: any = () => { };

  private valueType: 'value' | 'valueAsDate' = 'value';

  constructor(private renderer: Renderer2, private elementRef: ElementRef) { }

  registerOnTouched(fn: () => void) { this.onTouched = fn; }

  registerOnChange(fn: (value: any) => void) {
    this.onChange = (event: any) => fn(event.target[this.valueType])
  }

  writeValue(value: Date | string) {
    this.valueType = typeof value === 'string' ? 'value' : 'valueAsDate';
    this.renderer.setProperty(this.elementRef.nativeElement, this.valueType, value);
  }
}

With our directive instance, we can default to the native default string value.
However, we can dynamically switch this if the date input receives a date object. Once set as a date object, our directive will return date objects from our native input rather than date strings.

This gives us the flexibility to not break default native behavior but also adds Date object support to our date inputs.

@Component({
  selector: 'my-app',
  template: `
    <label for="date-string">Regular string value</label>
    <input [formControl]="dateStringControl" type="date" id="date-string" />

    <label for="date-object">Date object value with directive help</label>
    <input [formControl]="dateObjectControl" type="date" id="date-object" />
  `
})
export class AppComponent  {
  dateStringControl = new FormControl('2020-09-28');
  dateObjectControl = new FormControl(new Date());
}

Using Angular forms and directives, we can get the best of both worlds using JavaScript Date objects and built in date inputs. Check out the full working demo below!

View Demo Code   
Twitter Facebook LinkedIn Email
 

No spam. Short occasional updates on Web Development articles, videos, and new courses in your inbox.

Related Posts

Angular

Creating Dynamic Tables in Angular

Learn how to easily create HTML tables in Angular from dynamic data sources.

Read Article
Web Components

Reusable Component Patterns - Default Slots

Learn about how to use default slots in Web Components for a more flexible API design.

Read Article
Web Components

Reusable Component Anti-Patterns - Semantic Obfuscation

Learn about UI Component API design and one of the common anti-patterns, Semantic Obfuscation.

Read Article