65

I'm working with Angular 2 and I have this code:

JS, this code initiates the employee-variable for the template:

handleEmployee(employee : Employee){
        this.employee = employee;
        this.employee.startDate = new Date('2005/01/01');
        console.log(this.employee);
    }

Template:

...
<div>
    <label>Start date: </label>
    <input [(ngModel)]="employee.startDate" type="date" name="startDate"/>
  </div>
  <div>
...

Other data like firstname is displayed correctly. But for the date I just get:

mm/dd/yyyy

In the input element, which should be a date.

How can I do this?

1
  • When is handleEmployee called? Is employee.startDate initialized when the component is created (in the constructor or with the variable declaration)? Commented May 5, 2016 at 16:17

7 Answers 7

123

UPDATE:

StackBlitz

when I wrote this answer DatePipe did not exist, now you can just do this

<input [ngModel]="startDate | date:'yyyy-MM-dd'" (ngModelChange)="startDate = $event" type="date" name="startDate"/>

`


Old Answer:

PLUNKER

You need to convert date object in the input type="date" format which is yyyy-mm-dd, this is how it will work

Template:

<input [(ngModel)]="humanDate" type="date" name="startDate"/>

Component (TS):

export class App {
  startDate: any;

  constructor() {
    this.startDate = new Date(2005, 1, 4);
  }

  set humanDate(e){
    e = e.split('-');
    let d = new Date(Date.UTC(e[0], e[1]-1, e[2]));
    this.startDate.setFullYear(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
  }

  get humanDate(){
    return this.startDate.toISOString().substring(0, 10);
  }
}
Sign up to request clarification or add additional context in comments.

12 Comments

I had to modify this to use parseInt(e[1] -1 ) for the month, otherwise it would add a month on every time I chose a date. I guess this is because the month is zero - indexed in the Date constructor.
thanks @dafyddPrys. I updated the answer and PLUNKER, there was another issue caused by timeZone, now it should work perfectly. Just invalid dates are needed to be handled.
@A_Singh I had a further problem accounting for local timezone offsets being ignored in the ISOString. I had to calculate a time zone offset : (new Date()).getTimezoneOffset() * 60000 ; and then use that offset to print out the date : return (new Date(this.endDate.getTime() - tzOffset)).toISOString().slice(0,10); .... With that, I also didn't need to add 1 ti the day when setting setFulYear
...works for me under Angular 4. Ensure ngModel is without "()" -> oneWay binding and also ensure you're using (ngModelChange)="startDate = $event" then it works for me as described by Ankit Singh. thx
Updated answer works perfectly - clear and concise too
|
22

Read pipes and ngModel and my deсision:

<input type="date" class="form-control" id="myDate" [ngModel]="myDate | date:'y-MM-dd'" (ngModelChange)="myDate = $event" name="birthday">

3 Comments

OP should read your desicion? A good answer will always have an explanation of what was done and why it was done in such a manner, not only for the OP but for future visitors to SO.
@bub Perhaps the answer is not complete. This is my first experience. But in the pages of the official documentation has a detailed explanation of how to use ngModel and what it consists of.
You have had a great answer, simple e efficient. It's work perfectly.
8

FormControls (both template-driven and reactive) subscribe for values and write values via Directives that implement ControlValueAccessor. Take a look at the relevant method selectValueAccessor, which is used in all necessary directives. Normal input controls (e.g. <input type="text">) or textareas are handled by the DefaultValueAccessor. Another example is the CheckboxValueAccessor which is applied to checkbox input controls.

The job isn't complicated at all. We just need to implement a new value accessor for date input controls.
DateValueAccessor is a nice name:

// 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 attach the DateValueAccessor to the multi-provider DATE_VALUE_ACCESSOR, so that selectValueAccessor can find it.

The only question is, which selector should be used. I decided for an opt-in solution.
Here the DateValueAccessor selects on 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 is also possible to fix the default implementation.
The following selector would activate the feature magically.

// this selector changes the previous behavior silently and might break existing code
selector: 'input[type=date][formControlName],input[type=date][formControl],input[type=date][ngModel]'

But please be aware, that this might break existing implementations that rely of the old behaviour. So I would go for the opt-in version!

It's all on NPM and Github

For your convenience, I created the project angular-data-value-accessor on Github.
There is also a NPM package available:

npm install --save angular-date-value-accessor

Then just import the module via NgModule:

// app.module.ts

import { DateValueAccessorModule } from 'angular-date-value-accessor';

@NgModule({
  imports: [
    DateValueAccessorModule
  ]
})
export class AppModule { }

Now you can apply the "useValueAsDate" to your date input controls.

Demo

Of course, there is a demo at: http://johanneshoppe.github.io/angular-date-value-accessor/

2 Comments

Great stuff! Could this work for ng2-Bootstrap directives? [btnRadio] uses a string, so binding fails for ngModel properties that are number (enums).
haven't tested this for ng2-Bootstrap, but generally you can do pretty much by implementing your own ControlValueAccessor
3

I began trying to implement Ankit Singh's solution and ran in to a few problems with validation and timezone stuff. (Even after trying the suggestions in the comment section of that answer)

Instead I chose to utilize moment.js to handle the transforming between string and date using ISO8601 format date strings. I've had great results in the past using moment.js so this was not a difficult decision. Seems to be working well for me, hopefully someone else finds this useful.

For my Angular 2 app I ran npm install --save moment and then turned Ankit's solution into a wrapper around a js Date object:

import * as moment from 'moment';

export class NgDate {

    date: any;

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

    set dateInput(e) {
        if (e != "") {
            var momentDate = moment(e, moment.ISO_8601).toDate();
            this.date = momentDate;
        }
        else {
            this.date = null;
        }
    }

    get dateInput() {
        if(this.date == null)
        {
            return "";
        }

        var stringToReturn = moment(this.date).format().substring(0, 10);
        return stringToReturn;
    }
}

Then for the HTML:

<input type="date" name="someDate" [(ngModel)]="ngDateModel.dateInput"/>

Comments

3

I think the accepted answer lacks a function call to transform input date string to Date object. So this:

(ngModelChange)="startDate = $event"

Should be something like:

(ngModelChange)="startDate = toDate($event)"

I'm using Moment.js, which makes things MUCH easier:

my.component.ts

...
import * as moment from 'moment';
...
@Component ({
  ...
})
export class MyComponent implements OnInit {

  public fromDate: moment.Moment;
  public toDate: moment.Moment;
  
  ngOnInit() {
    this.toDate = moment();
    this.fromDate = moment().subtract(1, 'week');
  }

  dateStringToMoment(dateString: string): moment.Moment {
    return moment(dateString);
  }

my-component.html

...
<input type="date" min="{{ fromDate | date:'yyyy-MM-dd' }}" name="toDate" [ngModel]="toDate | date:'yyyy-MM-dd'" (ngModelChange)="toDate = dateStringToMoment($event)">
<input type="date" max="{{ toDate | date:'yyyy-MM-dd' }}" name="fromDate" [ngModel]="fromDate | date:'yyyy-MM-dd'" (ngModelChange)="fromDate = dateStringToMoment($event)">
...

1 Comment

Merci pour la clarté de contenu
2

Fixed it with this code:

handleEmployee(employee : Employee){
        this.employee = employee;

        let dateString : string = employee.startDate.toString();
        let days : number = parseInt(dateString.substring(8, 10));
        let months : number = parseInt(dateString.substring(5, 7));
        let years : number = parseInt(dateString.substring(0, 5));
        let goodDate : Date = new Date(years + "/" + months + "/" + days);
        goodDate.setDate(goodDate.getDate() + 2);
        this.date = goodDate.toISOString().substring(0, 10);
    }

Html:

<div>
    <label>Start date: </label>
    <input [(ngModel)]="date" type="date" name="startDate"/>
  </div>

Comments

0

Created a local string variable

dateField: string;

And I'm binding that to my form

Date input

<input type="text" class="form-control" required [(ngModel)]="dateField" />

Later just assign that back to the Date property just before making any API call

insert() {
    this.myObjet.date = new Date(this.dateField);
    ... call api

update() {
    this.myObjet.date = new Date(this.dateField);
    ... call api

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.