Multiple Date Select in Material datePicker (Angular)
G

4

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;
}
Goodsell answered 2/12, 2019 at 6:56 Comment(3)
check into this - npmjs.com/package/multiple-date-picker-angularAccustomed
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 -> material.angular.io/components/datepicker/overviewReckon
Thanks for your response. Is there any alternative way to do this in material date picker.Goodsell
S
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

Sheepcote answered 2/12, 2019 at 9:57 Comment(6)
you just nailed it! Good one:)Topo
@PrashantPimpale, thank for the correct, I just removed unnecesary imports and variablesSheepcote
thanks @Eliseo. Your solution was so helpful for me and solved my issue.Goodsell
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. :(Expedition
@Sheepcote 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.Delict
@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.NoneSheepcote
V
7

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.

Viniculture answered 24/1, 2020 at 18:37 Comment(2)
Or, you can use ngx-multiple-dates package. There are some examples of it.Viniculture
Thank you! I've been searching for solutions since yesterday.Anytime
S
1

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

Sperm answered 31/1, 2023 at 4:37 Comment(0)
D
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>
Distichous answered 5/1, 2023 at 17:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.