Géza
Géza

Reputation: 173

Angular material today button

My task is to put a today button on angular datepicker popup. (Selects the today date and closes popup)

<input matInput [matDatepicker]="toPicker" formControlName="validTo" >
<mat-datepicker-toggle matSuffix [for]="toPicker"></mat-datepicker-toggle>
<mat-datepicker #toPicker  >
   <mat-datepicker-actions>
    <button mat-button (click)="goToday()">Today</button>
  </mat-datepicker-actions> 
</mat-datepicker>

The angular function:

    @ViewChild('toPicker') toPicker: MatDatepicker<Date>;
  goToday() {
    this.toPicker.select(new Date());
    this.toPicker.close();
  }

This works! Unfortunately the default date selection is broken. I can click on a date but the popup remains open. Do you have idea how to add a today button and preserve the default funcionality

Upvotes: 3

Views: 2609

Answers (3)

J. S.
J. S.

Reputation: 2376

If anyone else encounters the same problem, I solved it by using the material overlay from CDK in combination with a mat-calendar. This approach offers much greater flexibility.

 <div
  class="flex items-center rounded-full bg-white text-black cursor-pointer px-3 py-1 mr-4"
  data-test="datepickerBtn"
  type="button"
  cdkOverlayOrigin
  #trigger="cdkOverlayOrigin"
  (click)="isOpen = !isOpen"
>
  <mat-hint>
    @if (isTodaySelected) {
      Today
    } @else {
      {{ selectedDate | date }}
    }
  </mat-hint>
  <mat-icon class="ml-1.5">event</mat-icon>
  <ng-template
    cdkConnectedOverlay
    [cdkConnectedOverlayPositions]="[position]"
    [cdkConnectedOverlayOffsetY]="5"
    [cdkConnectedOverlayOrigin]="trigger"
    [cdkConnectedOverlayOpen]="isOpen"
    (overlayOutsideClick)="isOpen = false"
  >
    <mat-card class="w-80 rounded-xl">
      <mat-calendar
        #calendar
        (selectedChange)="onSelectDate($event)"
        [startAt]="selectedDate"
        [selected]="selectedDate"
      />
      <div class="w-full flex justify-center mb-3">
        <button mat-button color="primary" (click)="onSelectDate()">
          Today
        </button>
      </div>
    </mat-card>
  </ng-template>
</div>

Component:

import { ConnectedPosition, OverlayModule } from '@angular/cdk/overlay';
import { CommonModule } from '@angular/common';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { TranslocoModule } from '@ngneat/transloco';
import { isSameDay } from 'date-fns';

@Component({
  selector: 'coa-org-datepicker',
  standalone: true,
  imports: [
    CommonModule,
    MatInputModule,
    MatDatepickerModule,
    MatIconModule,
    MatButtonModule,
    TranslocoModule,
    MatCardModule,
    OverlayModule,
  ],
  templateUrl: './datepicker.component.html',
})
export class DatepickerComponent {
  @Input() selectedDate: Date = new Date();
  @Output() dateSelected = new EventEmitter<Date>();

  isOpen = false;

  position: ConnectedPosition = {
    originX: 'end',
    originY: 'bottom',
    overlayX: 'end',
    overlayY: 'top',
  };

  onSelectDate(date: Date = new Date()): void {
    this.isOpen = false;
    this.dateSelected.emit(date);
  }

  get isTodaySelected() {
    return isSameDay(this.selectedDate, new Date());
  }
}

ConnectedPosition is only necessary if you don't want the overlay to attach to the bottom-left corner of your origin element. The div with the cdkOverlayOrigin directive can be any HTML element, such as a button, input, or any other element.

Here are the docs for the overlay part:

https://material.angular.io/cdk/overlay/api

This example also uses tailwind for css classes.

Upvotes: 2

Eliseo
Eliseo

Reputation: 57929

There a problem with mat-datepicker that it's how we can access to the mat-calendar

When we have a mat-date-picker the mat-calendar is in

 mat-date-picker._componentRef?.instance._calendar

With this idea we can use mat-datepicker-actions

<mat-form-field appearance="fill">
  <mat-label>Choose a date</mat-label>
  <input matInput [matDatepicker]="picker" [(ngModel)]="date" />
  <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
  <mat-datepicker #picker >
    <mat-datepicker-actions>
      <button mat-raised-button color="primary" (click)="selectToday(picker)">
        Today
      </button>
    </mat-datepicker-actions>
  </mat-datepicker>
</mat-form-field>

En selectToday we can use

  //see that we pass the mat-date-picker
  selectToday(picker:any)
  {
    picker._componentRef.instance._calendar.activeDate=new Date()
  }

But if we use mat-date-picker-actions we need know how close the mat-date-picker

So, when it's opened we can subscribe to selectedChange of the monthView

  getMonthView(picker:any)
  {
    setTimeout(()=>{
      this.sub && this.sub.unsubscribe()
      this.sub=picker._componentRef?.instance._calendar
               .monthView.selectedChange.subscribe((res:any)=>{
                  this.date=res;
                  picker.close()
                })
     })
  }

When we call to this function?

Well, we need call to this function when the mat-date-picker is openened and also when we selected a month -click in year and select a month-

So

<mat-datepicker #picker (opened)="getMonthView(picker)" 
                        (monthSelected)="getMonthView(picker)">
  ...
</mat-datepicker>

A stackblitz

Upvotes: 0

cuong vu
cuong vu

Reputation: 1

I have the same problem in my project and I have an idea. I hope it can help you.

@ViewChild('picker') datePicker: MatDatepicker<Date>;

goToday() {
    this.datePicker.select(new Date());
    this.datePicker.close();
  }

insertButton() {
    var matCalendar = document.getElementsByTagName("mat-calendar");
    if (matCalendar) {
      document.getElementsByTagName("mat-calendar")[0].insertAdjacentHTML('afterend', '<div class="m-2"><button id="today-button" style="width: 80px;float: right;">Today</button></div>')
      var todayButton = document.getElementById("today-button");
      todayButton.addEventListener("click", this.goToday.bind(this))
    }
  }

You can call the insertButton() in the click event of mat-datepicker-toggle and date picker input. It still work for me

Upvotes: 0

Related Questions