15

I have a requirement that a user can select multiple dates in a date picker. How can I implement multiple date select functionality in an Angular Material date picker?

date picker

I tried this through dateClass. But, after every date selection the date picker will be closed.

Here's what I tried

HTML code:

<input matInput [matDatepicker]="picker" placeholder="Choose a date">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker [dateClass]="dateClass" #picker></mat-datepicker>

Typescript code:

dateClass = (d: Date) => {
    const date = d.getDate();

    // Highlight the 1st and 20th day of each month.
    return (date === 1 || date === 5 || date === 14 || date === 19 || date === 21 ) ? 'example-custom-date-class' : undefined;
}
Dale K
  • 25,246
  • 15
  • 42
  • 71
Vinoth A
  • 173
  • 1
  • 1
  • 8
  • check into this - https://www.npmjs.com/package/multiple-date-picker-angular – Allabakash Dec 02 '19 at 07:16
  • Maybe you need to look for some alternative to this for selecting multiple dates through a mat date picker since, no such functionality is mentioned here -> https://material.angular.io/components/datepicker/overview – Mridul Dec 02 '19 at 07:18
  • Thanks for your response. Is there any alternative way to do this in material date picker. – Vinoth A Dec 02 '19 at 07:29

4 Answers4

25

You need work directly with the mat-calendar, you can enclosed in a mat menu and into a div to avoid "closed", see

<button mat-icon-button [matMenuTriggerFor]="appMenu">
  <mat-icon>calendar_today</mat-icon>
</button>
<mat-menu #appMenu="matMenu">
    <div (click)="$event.stopPropagation()">
        <mat-calendar #calendar 
           (selectedChange)="select($event,calendar)" 
            [dateClass]="isSelected">
        </mat-calendar>
    </div>
</mat-menu>

I choose store the values of the dates in a string in the way yyyy-MM-dd (*), so

Imports:

import { Component,ViewEncapsulation} from "@angular/core";

TS Code:

daysSelected: any[] = [];
event: any;

isSelected = (event: any) => {
  const date =
    event.getFullYear() +
    "-" +
    ("00" + (event.getMonth() + 1)).slice(-2) +
    "-" +
    ("00" + event.getDate()).slice(-2);
  return this.daysSelected.find(x => x == date) ? "selected" : null;
};

select(event: any, calendar: any) {
  const date =
    event.getFullYear() +
    "-" +
    ("00" + (event.getMonth() + 1)).slice(-2) +
    "-" +
    ("00" + event.getDate()).slice(-2);
  const index = this.daysSelected.findIndex(x => x == date);
  if (index < 0) this.daysSelected.push(date);
  else this.daysSelected.splice(index, 1);

  calendar.updateTodaysDate();
}

Finally the .css is simple:

.mat-calendar-body-cell.selected
{
  background-color:red!important;
  border-radius: 50%
}
.drop-calendar
{
  width:30rem
}

NOTE: Dont forget to set encapsulation to none in your component:

encapsulation:ViewEncapsulation.None

Update Why use ViewEncapsulation.None and other aproach use in styles.css

The problem is how put color to the date selected. When we use in mat-calendar [dateclass], we create a function that received as parameter the date (of each day of the month) and return a string with the name of the class you want. In the code, if the day is in the array selected, the class is 'selected'.

But this don't take account if we don't use ViewEncapsulation.None or we put in the styles.css (or styles.scss) (**). Yes, it's necesary that this style was defined in a "global" style. (remember that ViewEncapsulation.None make that the styles defined in the component becomes "global"

NOTE: If you "play" in stackblitz with ViewEncapsulation.None remember that you need refresh the stackblitz because the styles remain saved.

(**) remember in angular.json include in "styles"

"styles": [
 "src/styles.scss"
],

You can see in stackblitz

(*) you can choose, e.g. store the getTime() of the date selected. The idea is that you need find it in the array "daysSelected", else, if you use directy an object Date, you need compare year, month and day from date to the elements of the array. This give a poor perfomance. Think that the function "isSelected" is called how many times as days has a month, each time a click is done

Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • you just nailed it! Good one:) – Prashant Pimpale Dec 02 '19 at 12:11
  • 1
    @PrashantPimpale, thank for the correct, I just removed unnecesary imports and variables – Eliseo Dec 02 '19 at 12:26
  • thanks @Eliseo. Your solution was so helpful for me and solved my issue. – Vinoth A Dec 04 '19 at 09:23
  • After using `encapsulation:ViewEncapsulation.None` all my styles are getting affected. Is there any simple solution to it? If I use `::ng-deep` the above code working without using `encapsulation:ViewEncapsulation.None` but another catch is `::ng-deep` is depricated. :( – Shardul Jun 23 '20 at 14:39
  • @Eliseo pelase explain why adding `encapsulation:ViewEncapsulation.None` is important? I don't see any reason you have to add this in order to make it work. I removed it from your example code and nothing has changed. – Arthez Jun 24 '20 at 07:10
  • 1
    @Arthez, I try to explain in the updated of the answer. Basically you need put the class "global". You can also simple change the `styles.css` (or the `styles.scss`) and forget the ViewEncapsulation.None – Eliseo Jun 24 '20 at 08:03
5

One more way (kinda hack): StackBlitz

Just temporary rewriting close method to empty function, and return it back after change. Also calling weekdays rerendering function. Not safe and ideal solution, but works.

Might be useful for somebody.

UPD: Or, you can use ngx-multiple-dates package. There are some examples of it.

Ruslan Lekhman
  • 696
  • 5
  • 9
  • 1
    Or, you can use [ngx-multiple-dates](https://www.npmjs.com/package/ngx-multiple-dates) package. [There are some examples of it](https://lekhmanrus.github.io/ngx-multiple-dates/). – Ruslan Lekhman Jun 04 '21 at 16:05
0

This approach here is working as of Angular 13. Basically Angular provides a calendar component which displays the calendar as you know. The secret is that it has an "dateClass" property which has a call back. it will call the call back for each day of the month. You return the class that you want to apply to that day. This was designed with the intent that a calendar might have special days on it, like holidays. Lookup the "dateClass" and "MatCalendarCellClassFunction" for more information.

import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import {MatCalendarCellClassFunction} from '@angular/material/datepicker';

@Component({
  selector: 'app-habit-tracker',
  templateUrl: './habit-tracker.component.html',
  styleUrls: ['./habit-tracker.component.css'],
  encapsulation: ViewEncapsulation.None
})
export class HabitTrackerComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }

  selectedDates: string[] = []

  dateClass: MatCalendarCellClassFunction<Date> = (cellDate, view) => {
    if (view == 'month') {
      let dateToFind = this.getDateOnly(cellDate)
      let i = this.selectedDates.indexOf(dateToFind)
      if (i >= 0) {
        return 'selected'
      }
    }
    return ''
  }

  
  daySelected(date: Date | null,calendar: any) {
    if (date) {
      let dateSelected = this.getDateOnly(date)
      let i = this.selectedDates.indexOf(dateSelected)
      if (i >= 0) {
        this.selectedDates.splice(i,1)
      } else {
        this.selectedDates.push(dateSelected)
      }
      calendar.updateTodaysDate();
    }
  }

  getDateOnly(date: Date):string {
    return date.toISOString().split('T')[0];
  }

}

CSS

.mat-calendar-body-cell.selected
{
  background-color:#d6eafa;
  border-radius: 100%
}
.habit-tracker {
    width: 300px;
}
.mat-calendar-header {
    padding-top: 0px !important;
}

HTML

<div class="card habit-tracker mb-3">
    <mat-calendar 
    #calendar 
    (selectedChange)="daySelected($event,calendar)" 
    [dateClass]="dateClass"></mat-calendar>
</div>
David J
  • 1,018
  • 1
  • 9
  • 14
0

Use the Ruslan Lekhman code StackBlitz. For Angular >12, on line 43 change '_popupComponentRef' for '_componentRef'