Reputation: 173
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
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
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>
Upvotes: 0
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