Search code examples
angularangular-materialmat-datepicker

Implement variable binding in mat-datepicker


Description


I'm trying to wrap the MatDatePicker in a custom component called MyDatePicker so that I can use it in the html as the following.

Issue


The dateVariable is always undefined and seems the two-way binding I implemented below is not working. Once the user selects a new date from the picker, the setter gets called. However, the new value is not binded to the dateVariable.

Questions


  • How can I implement a two-way binding between my custom date picker and a variable?
  • How is it possible to implement a one-way binding (from date picker -> variable)?

Implementation


HTML file:

<my-datepicker [(selectedDate)]="dateVariable" placeholder="some text"></my-datepicker>

<button (click)="btnClick()">Show Selected Date</button>

The TS file:

//...

    dateVariable: string;       

    btnClick() {

      console.log('selected date:', this.dateVariable);
   }
//...

MyDatepicker.component.ts

export class MyDatepickerComponent {

    dateValue: string;
    @Input() placeholder: string;

    @Output() dateChange: EventEmitter<string> = new EventEmitter<string>();

    @Input()
    get selectedDate() {
        console.log('getter');
        return this.dateValue;
    }
    set selectedDate(val) {
        console.log('setter');
        this.dateValue = val;
        this.dateChange.emit(this.dateValue);
    }

    pickerEvent(event: MatDatepickerInputEvent<Date>) {
        this.selectedDate = event.value.toISOString();
    }
}

MyDatepicker.component.html:

<mat-form-field>
        <input matInput [matDatepicker]='pickerDate' placeholder='{{placeholder}}' (dateInput)="pickerEvent($event)">
        <mat-datepicker-toggle matSuffix [for]='pickerDate'></mat-datepicker-toggle>
        <mat-datepicker #pickerDate></mat-datepicker>
</mat-form-field>

Solution

  • As already stated: For the two-way binding [()] (banana-in-a-box syntax) we have to write an @Input with the corresponding @Output like:

    @Input() selectedDate: Date;
    
    @Output() selectedDateChange: EventEmitter<Date> = new EventEmitter<Date>();
    

    This can be used with an Input-Binding and a separate Output-Binding:

    <my-datepicker [selectedDate]="dateVariable" (selectedDateChange)="dateVariable = $event" placeholder="some text"></my-datepicker>
    
    

    Or with a little syntactic sugar provided by Angular with the banana-in-the-box syntax:

    <my-datepicker [(selectedDate)]="dateVariable" placeholder="some text"></my-datepicker>
    
    

    Aside Notes:

    1) I think you need to bind the matInput with the actual Date value as well

    <mat-form-field>
            <input matInput [matDatepicker]='pickerDate' [value]="selectedDate" placeholder='{{placeholder}}' (dateInput)="pickerEvent($event)">
            <mat-datepicker-toggle matSuffix [for]='pickerDate'></mat-datepicker-toggle>
            <mat-datepicker #pickerDate></mat-datepicker>
    </mat-form-field>
    

    2) If you want to use my-datepicker inside of an Angular form you need to implement the ControlValueAccessor interface

    3) If the only reason you are writing this custom component is that you want to use an ISO date string as input I would suggest handling this transformation on an API / Service level instead of in a component.