Skip to content

Commit 4e68b92

Browse files
committed
fix(material/datepicker): avoid re-rendering mat-calendar when minDate or maxDate is at a different time on the same day
Avoid re-rendering <mat-calendar/> when it receives a new value for either the [minDate] or [maxDate] Input, and the value is at a different time on the same day. Make two code changes to `MatCalendar` to accomplish this behavior change. - Normalize `_minDate` and `_maxDate` to midnight of the given day - Add check in `ngOnChanges` to re-render only if the current minDate is not equal to the previous minDate. Fixes #24435
1 parent 9166184 commit 4e68b92

File tree

2 files changed

+69
-3
lines changed

2 files changed

+69
-3
lines changed

src/material/datepicker/calendar.spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,16 @@ describe('MatCalendar', () => {
425425
expect(calendarInstance.monthView._init).toHaveBeenCalled();
426426
});
427427

428+
it('should not re-render the month view when the minDate changes to the same day at a different time', () => {
429+
fixture.detectChanges();
430+
spyOn(calendarInstance.monthView, '_init').and.callThrough();
431+
432+
testComponent.minDate = new Date(2016, JAN, 1, 0, 0, 0, 1);
433+
fixture.detectChanges();
434+
435+
expect(calendarInstance.monthView._init).not.toHaveBeenCalled();
436+
});
437+
428438
it('should re-render the month view when the maxDate changes', () => {
429439
fixture.detectChanges();
430440
spyOn(calendarInstance.monthView, '_init').and.callThrough();
@@ -473,6 +483,25 @@ describe('MatCalendar', () => {
473483
expect(calendarInstance.yearView._init).toHaveBeenCalled();
474484
});
475485

486+
it('should not re-render the year view when the maxDate changes to the same day at a different time', () => {
487+
fixture.detectChanges();
488+
const periodButton = calendarElement.querySelector(
489+
'.mat-calendar-period-button',
490+
) as HTMLElement;
491+
periodButton.click();
492+
fixture.detectChanges();
493+
494+
(calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click();
495+
fixture.detectChanges();
496+
497+
spyOn(calendarInstance.yearView, '_init').and.callThrough();
498+
499+
testComponent.maxDate = new Date(2018, JAN, 1, 0, 0, 1, 0);
500+
fixture.detectChanges();
501+
502+
expect(calendarInstance.yearView._init).not.toHaveBeenCalled();
503+
});
504+
476505
it('should re-render the multi-year view when the minDate changes', () => {
477506
fixture.detectChanges();
478507
const periodButton = calendarElement.querySelector(

src/material/datepicker/calendar.ts

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
OnDestroy,
2222
Optional,
2323
Output,
24+
SimpleChange,
2425
SimpleChanges,
2526
ViewChild,
2627
ViewEncapsulation,
@@ -255,19 +256,43 @@ export class MatCalendar<D> implements AfterContentInit, AfterViewChecked, OnDes
255256
return this._minDate;
256257
}
257258
set minDate(value: D | null) {
258-
this._minDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
259+
this._oldMinDate = this._minDate;
260+
261+
const validated = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
262+
if (validated === null) {
263+
this._minDate = null;
264+
} else {
265+
this._minDate = this._dateAdapter.createDate(
266+
this._dateAdapter.getYear(validated),
267+
this._dateAdapter.getMonth(validated),
268+
this._dateAdapter.getDate(validated),
269+
);
270+
}
259271
}
260272
private _minDate: D | null;
273+
private _oldMinDate: D | null;
261274

262275
/** The maximum selectable date. */
263276
@Input()
264277
get maxDate(): D | null {
265278
return this._maxDate;
266279
}
267280
set maxDate(value: D | null) {
268-
this._maxDate = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
281+
this._oldMaxDate = this._maxDate;
282+
283+
const validated = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
284+
if (validated === null) {
285+
this._maxDate = null;
286+
} else {
287+
this._maxDate = this._dateAdapter.createDate(
288+
this._dateAdapter.getYear(validated),
289+
this._dateAdapter.getMonth(validated),
290+
this._dateAdapter.getDate(validated),
291+
);
292+
}
269293
}
270294
private _maxDate: D | null;
295+
private _oldMaxDate: D | null;
271296

272297
/** Function used to filter which dates are selectable. */
273298
@Input() dateFilter: (date: D) => boolean;
@@ -393,7 +418,19 @@ export class MatCalendar<D> implements AfterContentInit, AfterViewChecked, OnDes
393418
}
394419

395420
ngOnChanges(changes: SimpleChanges) {
396-
const change = changes['minDate'] || changes['maxDate'] || changes['dateFilter'];
421+
const minDateChange: SimpleChange | undefined = !this._dateAdapter.sameDate(
422+
this._minDate,
423+
this._oldMinDate,
424+
)
425+
? changes['minDate']
426+
: undefined;
427+
const maxDateChange: SimpleChange | undefined = !this._dateAdapter.sameDate(
428+
this._maxDate,
429+
this._oldMaxDate,
430+
)
431+
? changes['maxDate']
432+
: undefined;
433+
const change = minDateChange || maxDateChange || changes['dateFilter'];
397434

398435
if (change && !change.firstChange) {
399436
const view = this._getCurrentViewComponent();

0 commit comments

Comments
 (0)