How to highlight custom months and years in Angular Material datepicker?

2.8k Views Asked by At

I have an array of days, which can be from any year. I'm trying to customize Angular Material datepicker to highlight some months in months selection view, and also some years in year selection view based on the array of days.

.html

<input [matDatepicker]="picker" />
<i class="fas fa-chevron-down"></i>
<mat-datepicker #picker></mat-datepicker>

.ts

arrayOfDays = [new Date('2019-01-01'), new Date('2020-08-10'),  new Date('2020-09-20')];

What I'm trying to achieve is to highlight 2019 and 2020 in year view, and if 2019 is selected, then highlight month JAN and if 2020 is selected, then highlight MAY & AUG in Red border except for current month and year like the below images.

month highlighting year highlighting

Is this possible?

I searched a lot and tried with dateClass, (yearSelected) event, etc but it is not applying for Months and Years.. If anyone know, how to do this kindly help..

FYI - https://stackblitz.com/edit/angular-date-class-filter-aactjv This is the one I found setting custom design to the dates view.. But i want similar implementation for Year and Months views.. Thanks in advance..

2

There are 2 best solutions below

6
On BEST ANSWER

Really it's a bit complex control the material angular date picker events. I don't know if there're a better aproach, but you can get it using a custom header component, see this SO

Before, we are going to create a dataService

@Injectable({
  providedIn: "root"
})
export class DataService {
  arrayOfDays = [
    new Date("2019-01-01"),
    new Date("2020-08-10"),
    new Date("2020-09-20")
  ];

  constructor() {}
  displayMonth(year: number, view: string) {
    setTimeout(() => {
      let elements = document.querySelectorAll(".mat-calendar-content");
      let x = elements[0].querySelectorAll(".mat-calendar-body-cell-content");
      if (view == "multi-year") {
        const years = this.arrayOfDays.map(x => x.getFullYear());
        x.forEach((y: any) => {
          if (years.indexOf(+y.innerHTML) >= 0) {
            {
              y.style["border-color"] = "red";
            }
          }
        });
      }
      if (view=="year"){
        const monthsIni = [
          "JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"
        ];
        const months = this.arrayOfDays
          .filter(x => x.getFullYear() == year)
          .map(x => monthsIni[x.getMonth()]);
        x.forEach((y: any) => {
          if (months.indexOf(y.innerHTML) >= 0) {
            {
              y.style["border-color"] = "red";
            }
          }
        });
      }
    });
  }
}

After inject the dataService in the ExampleHeader and in the component, in the Example header is a fwe complex but it's only write

  constructor(_intl: MatDatepickerIntl,
              @Inject(forwardRef(() => MatCalendar)) calendar: MatCalendar<any>,
              @Optional() _dateAdapter: DateAdapter<any>,
              @Optional() @Inject(MAT_DATE_FORMATS) _dateFormats: MatDateFormats,
              changeDetectorRef: ChangeDetectorRef
              ,private dataService:DataService
              ) {
    super(_intl,calendar,_dateAdapter,_dateFormats,changeDetectorRef)
  }

When handles user clicks on the period label, user clicks on the previous button and user clicks on the next button to call the service

  currentPeriodClicked(): void {
    this.calendar.currentView =
      this.calendar.currentView == "month" ? "multi-year" : "month";
    this.dataService.displayMonth(
      this.calendar.activeDate.getFullYear(),
      this.calendar.currentView
    );
  }

  customPrev(): void {
    this.previousClicked();
    this.dataService.displayMonth(
      this.calendar.activeDate.getFullYear(),
      this.calendar.currentView
    );
  }

  customNext(): void {
    this.nextClicked();
    this.dataService.displayMonth(
      this.calendar.activeDate.getFullYear(),
      this.calendar.currentView
    );
  }

Futhermore we need make it also in yearSelected from the main component

  yearSelected(event: any) {
    this.dataService.displayMonth(
      event.getFullYear(),
      this.calendar.currentView
    );
  }

You can see in the stackblitz

Update if only want to "remark" the months, the thing is more simple, because we need only control the (yearSelected) event, so we can forget about the custom header.

<mat-datepicker #picker [dateClass]="dateClass"
 (yearSelected)="yearSelected($event)"  
 [calendarHeaderComponent]="exampleHeader">
</mat-datepicker>

months = [0,3] //<--in a variable the months we want remark

yearSelected(event:any)
  {
    setTimeout(()=>{
        let elements = document.querySelectorAll(".mat-calendar-content");
        let x = elements[0].querySelectorAll(".mat-calendar-body-cell-content");
        x.forEach((y: any,month:number) => {
          if (this.months.indexOf(month) >= 0) {
            {
              y.style["border-color"] = "red";
            }
          }
        });
      }
    )
  }

see stackblitz

1
On

override getMonthNames(_style: 'long' | 'short' | 'narrow') { return getLocaleMonthNames(this.languageService.selectedLanguage, FormStyle.Format, TranslationWidth.Abbreviated) as string[]; }

in your dateAdapter you can override the method and return any 12 strings you want