Search code examples
angularsassangular-materialcalendar

Customizing selected dates in mat calendar (Angular material)


I need to mark the days that have any task in the mat calendar, but i can´t find why is not working this code.

here goes the ts

 dateClass(): any {
    return (date: Date): MatCalendarCellCssClasses => {
      const findDate = this.tasks.find((task) =>
        moment(task.date_required.toDate()).isSame(moment(date), 'day')
      );
      if (findDate !== undefined) {
        console.log(findDate)
        return 'date-with-task';
      } else {
        return;
      }
    };
  }

these are the component imports

import {
  ChangeDetectorRef,
  Component,
  EventEmitter,
  HostListener,
  Input,
  OnChanges,
  OnInit,
  Output,
  SimpleChanges,
  ViewEncapsulation,
} from '@angular/core';

the variable tasks is an input from a parent component

  @Input() tasks: any[];

and here is the html where i use the mat calendar

 <div class="calendar-container" [ngStyle]="{'display': !showingCalendar ? 'none' : ''}">
        <mat-calendar class="calendar-tasks" [dateClass]="dateClass()" [(selected)]="selectedDay"
            (selectedChange)='selectedChange($event)' (monthSelected)='monthSelected($event)' [startAt]='currentMonth'>
        </mat-calendar>
        <div class="border-bottom"></div>
    </div>

also, i made this class in scss so i could see if the code it´s working

  .date-with-task{
    border-color: red;
  }

i read all the forums i could find and i also watched some yt videos about mat calendas, but i couldn´t find the answer. If someone could help me i'll be really grateful :)

i needed to find the days with tasks marked in red, but nothing happened


Solution

  • A few changes are required for your code to work as intended:

    1. Use an arrow function

      You're currently using a regular function. Inside it, this is the function's scope, not the class instance.
      For access to the class instance via this, you have to use an arrow function 1:

    export class MyComponent {
      @Input() tasks: any[];
    
      dateClass = (date: Date): MatCalendarCellCssClasses => {
        console.log(this.tasks) // inside your function was `undefined`
        if (someCondition) {
          return 'some-class'
        }
        return ''
      }
    }
    
    1. You probably don't want to style the <button />
      The class returned by the above function will be applied to button.mat-calendar-body-cell. If you style those buttons, two adjacent dates will touch each other. I suggest you style the inner cell content (.mat-calendar-body-cell-content):
    .test-class .mat-calendar-body-cell-content {
      border-color: red;
    }
    
    • If you decide to style the buttons, not the cell content, note they are currently styled using border: none, which means you need to set more than border-color for the border to become visible (border: 1px solid red; would work); additionally, you might want to add some border-radius (they're boxy by default).
    1. Un-scope your (S)CSS,
      ... because you're not styling your component's DOM elements, but inner DOM elements of <mat-calendar>. To remove the CSS scoping, use ViewEncapsulation.None (or add your styles to an un-scoped stylesheet):
    @Component({
      encapsulation: ViewEncapsulation.None
    })
    

    Working example 2, 3.


    1 - The function should be modified to return a string according to your business logic.
    2 - The more verbose version of dateClass in the example would be:

    dateClass = (date: Date): MatCalendarCellCssClasses => {
      if (
        this.tasks
          .map((t) => t.date_required)
          .find((d) => moment(d).isSame(date, 'day'))
      ) {
        return 'test-class'
      }
      return ''
    }
    

    3 - Alternatively, you could type dateClass as MatCalendarCellClassFunction<Date>:

    dateClass: MatCalendarCellClassFunction<Date> = (date) =>
      (this.tasks
        .map((t) => t.date_required)
        .find((d) => moment(d).isSame(date, 'day')) &&
        'test-class') ||
      '';