Bind an input with type datetime-local to a Date property in Angular 2

92,423

Solution 1

Demo Plnkr

You can bind to a date using the following format: yyyy-MM-ddTHH:mm, which you can also get from date.toISOString().slice(0,16) (the slice removes the time portion after the minutes).

@Component({
    selector: 'app',
    template: `<input type="datetime-local" [value]="date" 
          (change)="date=$event.target.value" /> {{date}}` 
})
export class AppComponent {
    date: string;
    constructor() {
        this.date = new Date().toISOString().slice(0, 16);
    }
}

Keep in mind that date.toISOString() will return a date offset from local time. You can also construct the date string yourself:

private toDateString(date: Date): string {
    return (date.getFullYear().toString() + '-' 
       + ("0" + (date.getMonth() + 1)).slice(-2) + '-' 
       + ("0" + (date.getDate())).slice(-2))
       + 'T' + date.toTimeString().slice(0,5);
}

If you want to be able to bind the select to a Date model, you can use this to build a custom date component:

@Component({
    selector: 'my-date',
    events: ['dateChange'],
    template: `<input type="datetime-local" [value] = "_date" 
             (change) = "onDateChange($event.target.value)" />`
})
export class MyDate{
    private _date: string;
    @Input() set date(d: Date) {
        this._date = this.toDateString(d);
    }
    @Output() dateChange: EventEmitter<Date>;
    constructor() {
        this.date = new Date();
        this.dateChange = new EventEmitter();       
    }

    private toDateString(date: Date): string {
        return (date.getFullYear().toString() + '-' 
           + ("0" + (date.getMonth() + 1)).slice(-2) + '-' 
           + ("0" + (date.getDate())).slice(-2))
           + 'T' + date.toTimeString().slice(0,5);
    }

    private parseDateString(date:string): Date {
       date = date.replace('T','-');
       var parts = date.split('-');
       var timeParts = parts[3].split(':');

      // new Date(year, month [, day [, hours[, minutes[, seconds[, ms]]]]])
      return new Date(parts[0], parts[1]-1, parts[2], timeParts[0], timeParts[1]);     // Note: months are 0-based

    }

    private onDateChange(value: string): void {
        if (value != this._date) {
            var parsedDate = this.parseDateString(value);

            // check if date is valid first
            if (parsedDate.getTime() != NaN) {
               this._date = value;
               this.dateChange.emit(parsedDate);
            }
        }
    }
}

Users of your component would bind to a Date model with two-way model binding:

@Component({
    selector: 'my-app',
    directives: [MyDate],
    template: '<my-date [(date)]="date"></my-date>  {{date}}' 
})
export class AppComponent {
    @Input() date: Date;
    constructor() {
        this.date = new Date();
    }
}

Or if you want to avoid custom tags, rewrite the component as a directive:

<input type="datetime-local" [(date)]="date" />

Demo Plnkr with Directive

Solution 2

Now that its Spring 2017, DatePipe is shipped OOTB. You can achieve (one-way) binding by specifying format parameters to the pipe. For example:

<input type="datetime-local" [ngModel]="filterDateFrom | date:'yyyy-MM-ddTHH:mm'" />

Slight caveat is that you can not use two-way binding with this technique, you have to use one way binding with the data pipe, then manage the DOM to model change events to handle client changes to the control (unless I'm missing something!), but it seems a lot cleaner this way.


Update

Looks like I was indeed missing something!

Adding ngModelChange to the above should provide the DOM --> model side of the two-way binding process:

<input type="datetime-local" 
       [ngModel]="filterDateFrom | date:'yyyy-MM-ddTHH:mm'"
       (ngModelChange)="filterDateFrom = $event" />

Solution 3

Inspired by @ne1410s answer I ended doing something very similar but without losing the date type.

I used a pipe to declare the ngModel and call a method dateChanged just to return the conversion of the new Date in the ts.

html code:

<input type="datetime-local" [ngModel]="filterDateFrom | date:'yyyy-MM-ddTHH:mm'" (ngModelChange)="filterDateFrom = dateChanged($event)"/>

ts code:

 dateChanged(eventDate: string): Date | null {
   return !!eventDate ? new Date(eventDate) : null;
 }

Solution 4

I was looking into this problem as well and started to go down this examples road. However, you can use [(ngModel)] on an input of the type [date,datetime,datetime-local]. The key is to match the expected format the control is expecting. In this case it expects this format. Which also means the type that you bind to the control needs to be a string. I have provided an example plunker, that demonstrates how to use [(ngModel)].

import { Component } from 'angular2/core';

@Component({
  selector: 'my-app',
  template: `
      <form>
        <input type="datetime-local" [(ngModel)]="dateTimeLocal"><br />
        {{dateTimeLocal}}
      </form>
    `
})
export class AppComponent {
  private _dateTimeLocal: Date;

  constructor() {
    this._dateTimeLocal = new Date();
  }

  private parseDateToStringWithFormat(date: Date): string {
    let result: string;
    let dd = date.getDate().toString();
    let mm = (date.getMonth() + 1).toString();
    let hh = date.getHours().toString();
    let min = date.getMinutes().toString();
    dd = dd.length === 2 ? dd : "0" + dd;
    mm = mm.length === 2 ? mm : "0" + mm;
    hh = hh.length === 2 ? hh : "0" + hh;
    min = min.length === 2 ? min : "0" + min;
    result = [date.getFullYear(), '-', mm, '-', dd, 'T', hh, ':', min].join('');

    return result;
  }

  public set dateTimeLocal(v: string) {
    let actualParsedDate = v ? new Date(v) : new Date();
    let normalizedParsedDate = new Date(actualParsedDate.getTime() + (actualParsedDate.getTimezoneOffset() * 60000));
    this._dateTimeLocal = normalizedParsedDate;
  }


  public get dateTimeLocal(): string {
    return this.parseDateToStringWithFormat(this._dateTimeLocal);
  }
}
Share:
92,423
Dominik Palo
Author by

Dominik Palo

My interests: Programming (especially mobile apps, Xamarin, .NET C#, WPF and Node.js), 3D printing (RepRap) and open-hardware

Updated on March 28, 2020

Comments

  • Dominik Palo
    Dominik Palo about 4 years

    It is possible to bind a component property of Date type to a HTML5 input with type set to datetime-local?

    In my component I have a poperty:

    public filterDateFrom: Date;
    

    and in my template I have an input defined as:

    <input type="datetime-local" [(ngModel)]="filterDateFrom" />
    

    but binding doesn't work.