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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.