Skip to content

Commit 39e3c4e

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 8436f8c commit 39e3c4e

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
@@ -517,6 +517,57 @@ describe('MdCalendar', () => {
517517

518518
expect(calendarInstance._activeDate).toEqual(new Date(2018, JAN, 1));
519519
});
520+
521+
it('should re-render the month view when the minDate changes', () => {
522+
fixture.detectChanges();
523+
spyOn(calendarInstance.monthView, '_init').and.callThrough();
524+
525+
testComponent.minDate = new Date(2017, NOV, 1);
526+
fixture.detectChanges();
527+
528+
expect(calendarInstance.monthView._init).toHaveBeenCalled();
529+
});
530+
531+
it('should re-render the month view when the maxDate changes', () => {
532+
fixture.detectChanges();
533+
spyOn(calendarInstance.monthView, '_init').and.callThrough();
534+
535+
testComponent.maxDate = new Date(2017, DEC, 1);
536+
fixture.detectChanges();
537+
538+
expect(calendarInstance.monthView._init).toHaveBeenCalled();
539+
});
540+
541+
it('should re-render the year view when the minDate changes', () => {
542+
fixture.detectChanges();
543+
const periodButton =
544+
calendarElement.querySelector('.mat-calendar-period-button') as HTMLElement;
545+
periodButton.click();
546+
fixture.detectChanges();
547+
548+
spyOn(calendarInstance.yearView, '_init').and.callThrough();
549+
550+
testComponent.minDate = new Date(2017, NOV, 1);
551+
fixture.detectChanges();
552+
553+
expect(calendarInstance.yearView._init).toHaveBeenCalled();
554+
});
555+
556+
it('should re-render the year view when the maxDate changes', () => {
557+
fixture.detectChanges();
558+
const periodButton =
559+
calendarElement.querySelector('.mat-calendar-period-button') as HTMLElement;
560+
periodButton.click();
561+
fixture.detectChanges();
562+
563+
spyOn(calendarInstance.yearView, '_init').and.callThrough();
564+
565+
testComponent.maxDate = new Date(2017, DEC, 1);
566+
fixture.detectChanges();
567+
568+
expect(calendarInstance.yearView._init).toHaveBeenCalled();
569+
});
570+
520571
});
521572

522573
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,6 +31,9 @@ import {
3131
Optional,
3232
Output,
3333
ViewEncapsulation,
34+
ViewChild,
35+
OnChanges,
36+
SimpleChanges,
3437
} from '@angular/core';
3538
import {
3639
DateAdapter,
@@ -43,6 +46,8 @@ import {Subscription} from 'rxjs/Subscription';
4346
import {coerceDateProperty} from './coerce-date-property';
4447
import {createMissingDateImplError} from './datepicker-errors';
4548
import {MdDatepickerIntl} from './datepicker-intl';
49+
import {MdMonthView} from './month-view';
50+
import {MdYearView} from './year-view';
4651

4752

4853
/**
@@ -61,7 +66,7 @@ import {MdDatepickerIntl} from './datepicker-intl';
6166
preserveWhitespaces: false,
6267
changeDetection: ChangeDetectionStrategy.OnPush,
6368
})
64-
export class MdCalendar<D> implements AfterContentInit, OnDestroy {
69+
export class MdCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
6570
private _intlChanges: Subscription;
6671

6772
/** A date representing the period (month or year) to start the calendar in. */
@@ -100,6 +105,12 @@ export class MdCalendar<D> implements AfterContentInit, OnDestroy {
100105
/** Emits when any date is selected. */
101106
@Output() userSelection = new EventEmitter<void>();
102107

108+
/** Reference to the current month view component. */
109+
@ViewChild(MdMonthView) monthView: MdMonthView<D>;
110+
111+
/** Reference to the current year view component. */
112+
@ViewChild(MdYearView) yearView: MdYearView<D>;
113+
103114
/** Date filter for the month and year views. */
104115
_dateFilterForViews = (date: D) => {
105116
return !!date &&
@@ -172,6 +183,18 @@ export class MdCalendar<D> implements AfterContentInit, OnDestroy {
172183
this._intlChanges.unsubscribe();
173184
}
174185

186+
ngOnChanges(changes: SimpleChanges) {
187+
const change = changes.minDate || changes.maxDate || changes.dateFilter;
188+
189+
if (change && !change.firstChange) {
190+
const view = this.monthView || this.yearView;
191+
192+
if (view) {
193+
view._init();
194+
}
195+
}
196+
}
197+
175198
/** Handles date selection in the month view. */
176199
_dateSelected(date: D): void {
177200
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, MD_DATE_FORMATS, MdDateFormats} from '@angular/material/core';
2122
import {MdCalendarCell} from './calendar-body';
@@ -93,7 +94,8 @@ export class MdMonthView<D> implements AfterContentInit {
9394
_weekdays: {long: string, narrow: string}[];
9495

9596
constructor(@Optional() public _dateAdapter: DateAdapter<D>,
96-
@Optional() @Inject(MD_DATE_FORMATS) private _dateFormats: MdDateFormats) {
97+
@Optional() @Inject(MD_DATE_FORMATS) private _dateFormats: MdDateFormats,
98+
private _changeDetectorRef: ChangeDetectorRef) {
9799
if (!this._dateAdapter) {
98100
throw createMissingDateImplError('DateAdapter');
99101
}
@@ -132,7 +134,7 @@ export class MdMonthView<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 MdMonthView<D> implements AfterContentInit {
146148
this._dateAdapter.getFirstDayOfWeek()) % DAYS_PER_WEEK;
147149

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

151154
/** Creates MdCalendarCells 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, MD_DATE_FORMATS, MdDateFormats} from '@angular/material/core';
2122
import {MdCalendarCell} from './calendar-body';
@@ -79,7 +80,8 @@ export class MdYearView<D> implements AfterContentInit {
7980
_selectedMonth: number | null;
8081

8182
constructor(@Optional() public _dateAdapter: DateAdapter<D>,
82-
@Optional() @Inject(MD_DATE_FORMATS) private _dateFormats: MdDateFormats) {
83+
@Optional() @Inject(MD_DATE_FORMATS) private _dateFormats: MdDateFormats,
84+
private _changeDetectorRef: ChangeDetectorRef) {
8385
if (!this._dateAdapter) {
8486
throw createMissingDateImplError('DateAdapter');
8587
}
@@ -104,7 +106,7 @@ export class MdYearView<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 MdYearView<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)