Skip to content

Commit d971c0a

Browse files
committed
fix(calendar): not reacting to min/max boundary changes
Fixes the calendar not re-rendering when the `minDate`, `maxDate` or `dateFilter` change. The issue was due to the fact that the `minDate`, `maxDate` and `dateFilter` weren't being passed down to the views via `@Input`. Fixes #7202.
1 parent 53c42a4 commit d971c0a

File tree

4 files changed

+85
-5
lines changed

4 files changed

+85
-5
lines changed

src/lib/datepicker/calendar.spec.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,57 @@ describe('MatCalendar', () => {
528528

529529
expect(calendarInstance._activeDate).toEqual(new Date(2018, JAN, 1));
530530
});
531+
532+
it('should re-render the month view when the minDate changes', () => {
533+
fixture.detectChanges();
534+
spyOn(calendarInstance.monthView, '_init').and.callThrough();
535+
536+
testComponent.minDate = new Date(2017, NOV, 1);
537+
fixture.detectChanges();
538+
539+
expect(calendarInstance.monthView._init).toHaveBeenCalled();
540+
});
541+
542+
it('should re-render the month view when the maxDate changes', () => {
543+
fixture.detectChanges();
544+
spyOn(calendarInstance.monthView, '_init').and.callThrough();
545+
546+
testComponent.maxDate = new Date(2017, DEC, 1);
547+
fixture.detectChanges();
548+
549+
expect(calendarInstance.monthView._init).toHaveBeenCalled();
550+
});
551+
552+
it('should re-render the year view when the minDate changes', () => {
553+
fixture.detectChanges();
554+
const periodButton =
555+
calendarElement.querySelector('.mat-calendar-period-button') as HTMLElement;
556+
periodButton.click();
557+
fixture.detectChanges();
558+
559+
spyOn(calendarInstance.yearView, '_init').and.callThrough();
560+
561+
testComponent.minDate = new Date(2017, NOV, 1);
562+
fixture.detectChanges();
563+
564+
expect(calendarInstance.yearView._init).toHaveBeenCalled();
565+
});
566+
567+
it('should re-render the year view when the maxDate changes', () => {
568+
fixture.detectChanges();
569+
const periodButton =
570+
calendarElement.querySelector('.mat-calendar-period-button') as HTMLElement;
571+
periodButton.click();
572+
fixture.detectChanges();
573+
574+
spyOn(calendarInstance.yearView, '_init').and.callThrough();
575+
576+
testComponent.maxDate = new Date(2017, DEC, 1);
577+
fixture.detectChanges();
578+
579+
expect(calendarInstance.yearView._init).toHaveBeenCalled();
580+
});
581+
531582
});
532583

533584
describe('calendar with date filter', () => {

src/lib/datepicker/calendar.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,18 @@ import {
3131
Optional,
3232
Output,
3333
ViewEncapsulation,
34+
ViewChild,
35+
OnChanges,
36+
SimpleChanges,
3437
} from '@angular/core';
3538
import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core';
3639
import {first} from 'rxjs/operator/first';
3740
import {Subscription} from 'rxjs/Subscription';
3841
import {coerceDateProperty} from './coerce-date-property';
3942
import {createMissingDateImplError} from './datepicker-errors';
4043
import {MatDatepickerIntl} from './datepicker-intl';
44+
import {MatMonthView} from './month-view';
45+
import {MatYearView} from './year-view';
4146

4247

4348
/**
@@ -56,7 +61,7 @@ import {MatDatepickerIntl} from './datepicker-intl';
5661
preserveWhitespaces: false,
5762
changeDetection: ChangeDetectionStrategy.OnPush,
5863
})
59-
export class MatCalendar<D> implements AfterContentInit, OnDestroy {
64+
export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
6065
private _intlChanges: Subscription;
6166

6267
/** A date representing the period (month or year) to start the calendar in. */
@@ -95,6 +100,12 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy {
95100
/** Emits when any date is selected. */
96101
@Output() userSelection = new EventEmitter<void>();
97102

103+
/** Reference to the current month view component. */
104+
@ViewChild(MatMonthView) monthView: MatMonthView<D>;
105+
106+
/** Reference to the current year view component. */
107+
@ViewChild(MatYearView) yearView: MatYearView<D>;
108+
98109
/** Date filter for the month and year views. */
99110
_dateFilterForViews = (date: D) => {
100111
return !!date &&
@@ -166,6 +177,18 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy {
166177
this._intlChanges.unsubscribe();
167178
}
168179

180+
ngOnChanges(changes: SimpleChanges) {
181+
const change = changes.minDate || changes.maxDate || changes.dateFilter;
182+
183+
if (change && !change.firstChange) {
184+
const view = this.monthView || this.yearView;
185+
186+
if (view) {
187+
view._init();
188+
}
189+
}
190+
}
191+
169192
/** Handles date selection in the month view. */
170193
_dateSelected(date: D): void {
171194
if (!this._dateAdapter.sameDate(date, this.selected)) {

src/lib/datepicker/month-view.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
Optional,
1717
Output,
1818
ViewEncapsulation,
19+
ChangeDetectorRef,
1920
} from '@angular/core';
2021
import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core';
2122
import {MatCalendarCell} from './calendar-body';
@@ -93,7 +94,8 @@ export class MatMonthView<D> implements AfterContentInit {
9394
_weekdays: {long: string, narrow: string}[];
9495

9596
constructor(@Optional() public _dateAdapter: DateAdapter<D>,
96-
@Optional() @Inject(MAT_DATE_FORMATS) private _dateFormats: MatDateFormats) {
97+
@Optional() @Inject(MAT_DATE_FORMATS) private _dateFormats: MatDateFormats,
98+
private _changeDetectorRef: ChangeDetectorRef) {
9799
if (!this._dateAdapter) {
98100
throw createMissingDateImplError('DateAdapter');
99101
}
@@ -132,7 +134,7 @@ export class MatMonthView<D> implements AfterContentInit {
132134
}
133135

134136
/** Initializes this month view. */
135-
private _init() {
137+
_init() {
136138
this._selectedDate = this._getDateInCurrentMonth(this.selected);
137139
this._todayDate = this._getDateInCurrentMonth(this._dateAdapter.today());
138140
this._monthLabel =
@@ -146,6 +148,7 @@ export class MatMonthView<D> implements AfterContentInit {
146148
this._dateAdapter.getFirstDayOfWeek()) % DAYS_PER_WEEK;
147149

148150
this._createWeekCells();
151+
this._changeDetectorRef.markForCheck();
149152
}
150153

151154
/** Creates MatCalendarCells for the dates in this month. */

src/lib/datepicker/year-view.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
Optional,
1717
Output,
1818
ViewEncapsulation,
19+
ChangeDetectorRef,
1920
} from '@angular/core';
2021
import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core';
2122
import {MatCalendarCell} from './calendar-body';
@@ -79,7 +80,8 @@ export class MatYearView<D> implements AfterContentInit {
7980
_selectedMonth: number | null;
8081

8182
constructor(@Optional() public _dateAdapter: DateAdapter<D>,
82-
@Optional() @Inject(MAT_DATE_FORMATS) private _dateFormats: MatDateFormats) {
83+
@Optional() @Inject(MAT_DATE_FORMATS) private _dateFormats: MatDateFormats,
84+
private _changeDetectorRef: ChangeDetectorRef) {
8385
if (!this._dateAdapter) {
8486
throw createMissingDateImplError('DateAdapter');
8587
}
@@ -104,7 +106,7 @@ export class MatYearView<D> implements AfterContentInit {
104106
}
105107

106108
/** Initializes this month view. */
107-
private _init() {
109+
_init() {
108110
this._selectedMonth = this._getMonthInCurrentYear(this.selected);
109111
this._todayMonth = this._getMonthInCurrentYear(this._dateAdapter.today());
110112
this._yearLabel = this._dateAdapter.getYearName(this.activeDate);
@@ -113,6 +115,7 @@ export class MatYearView<D> implements AfterContentInit {
113115
// First row of months only contains 5 elements so we can fit the year label on the same row.
114116
this._months = [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]].map(row => row.map(
115117
month => this._createCellForMonth(month, monthNames[month])));
118+
this._changeDetectorRef.markForCheck();
116119
}
117120

118121
/**

0 commit comments

Comments
 (0)