From be2e1f52063d61929054e993f9e839da844d01d5 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Thu, 21 Sep 2017 21:43:55 +0200 Subject: [PATCH] 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. --- src/lib/datepicker/calendar.spec.ts | 51 +++++++++++++++++++++++++++++ src/lib/datepicker/calendar.ts | 25 +++++++++++++- src/lib/datepicker/month-view.ts | 7 ++-- src/lib/datepicker/year-view.ts | 7 ++-- 4 files changed, 85 insertions(+), 5 deletions(-) diff --git a/src/lib/datepicker/calendar.spec.ts b/src/lib/datepicker/calendar.spec.ts index 686fa406de13..39055ff985f9 100644 --- a/src/lib/datepicker/calendar.spec.ts +++ b/src/lib/datepicker/calendar.spec.ts @@ -529,6 +529,57 @@ describe('MdCalendar', () => { expect(calendarInstance._activeDate).toEqual(new Date(2018, JAN, 1)); }); + + it('should re-render the month view when the minDate changes', () => { + fixture.detectChanges(); + spyOn(calendarInstance.monthView, '_init').and.callThrough(); + + testComponent.minDate = new Date(2017, NOV, 1); + fixture.detectChanges(); + + expect(calendarInstance.monthView._init).toHaveBeenCalled(); + }); + + it('should re-render the month view when the maxDate changes', () => { + fixture.detectChanges(); + spyOn(calendarInstance.monthView, '_init').and.callThrough(); + + testComponent.maxDate = new Date(2017, DEC, 1); + fixture.detectChanges(); + + expect(calendarInstance.monthView._init).toHaveBeenCalled(); + }); + + it('should re-render the year view when the minDate changes', () => { + fixture.detectChanges(); + const periodButton = + calendarElement.querySelector('.mat-calendar-period-button') as HTMLElement; + periodButton.click(); + fixture.detectChanges(); + + spyOn(calendarInstance.yearView, '_init').and.callThrough(); + + testComponent.minDate = new Date(2017, NOV, 1); + fixture.detectChanges(); + + expect(calendarInstance.yearView._init).toHaveBeenCalled(); + }); + + it('should re-render the year view when the maxDate changes', () => { + fixture.detectChanges(); + const periodButton = + calendarElement.querySelector('.mat-calendar-period-button') as HTMLElement; + periodButton.click(); + fixture.detectChanges(); + + spyOn(calendarInstance.yearView, '_init').and.callThrough(); + + testComponent.maxDate = new Date(2017, DEC, 1); + fixture.detectChanges(); + + expect(calendarInstance.yearView._init).toHaveBeenCalled(); + }); + }); describe('calendar with date filter', () => { diff --git a/src/lib/datepicker/calendar.ts b/src/lib/datepicker/calendar.ts index b66adc2ff4f3..86d270d8d017 100644 --- a/src/lib/datepicker/calendar.ts +++ b/src/lib/datepicker/calendar.ts @@ -31,6 +31,9 @@ import { Optional, Output, ViewEncapsulation, + ViewChild, + OnChanges, + SimpleChanges, } from '@angular/core'; import { DateAdapter, @@ -43,6 +46,8 @@ import {Subscription} from 'rxjs/Subscription'; import {coerceDateProperty} from './coerce-date-property'; import {createMissingDateImplError} from './datepicker-errors'; import {MdDatepickerIntl} from './datepicker-intl'; +import {MdMonthView} from './month-view'; +import {MdYearView} from './year-view'; /** @@ -62,7 +67,7 @@ import {MdDatepickerIntl} from './datepicker-intl'; changeDetection: ChangeDetectionStrategy.OnPush, viewProviders: [{provide: MATERIAL_COMPATIBILITY_MODE, useValue: true}], }) -export class MdCalendar implements AfterContentInit, OnDestroy { +export class MdCalendar implements AfterContentInit, OnDestroy, OnChanges { private _intlChanges: Subscription; /** A date representing the period (month or year) to start the calendar in. */ @@ -101,6 +106,12 @@ export class MdCalendar implements AfterContentInit, OnDestroy { /** Emits when any date is selected. */ @Output() userSelection = new EventEmitter(); + /** Reference to the current month view component. */ + @ViewChild(MdMonthView) monthView: MdMonthView; + + /** Reference to the current year view component. */ + @ViewChild(MdYearView) yearView: MdYearView; + /** Date filter for the month and year views. */ _dateFilterForViews = (date: D) => { return !!date && @@ -172,6 +183,18 @@ export class MdCalendar implements AfterContentInit, OnDestroy { this._intlChanges.unsubscribe(); } + ngOnChanges(changes: SimpleChanges) { + const change = changes.minDate || changes.maxDate || changes.dateFilter; + + if (change && !change.firstChange) { + const view = this.monthView || this.yearView; + + if (view) { + view._init(); + } + } + } + /** Handles date selection in the month view. */ _dateSelected(date: D): void { if (!this._dateAdapter.sameDate(date, this.selected)) { diff --git a/src/lib/datepicker/month-view.ts b/src/lib/datepicker/month-view.ts index 6030f1a45de4..6651a92b7a47 100644 --- a/src/lib/datepicker/month-view.ts +++ b/src/lib/datepicker/month-view.ts @@ -16,6 +16,7 @@ import { Optional, Output, ViewEncapsulation, + ChangeDetectorRef, } from '@angular/core'; import { DateAdapter, MATERIAL_COMPATIBILITY_MODE, MD_DATE_FORMATS, @@ -97,7 +98,8 @@ export class MdMonthView implements AfterContentInit { _weekdays: {long: string, narrow: string}[]; constructor(@Optional() public _dateAdapter: DateAdapter, - @Optional() @Inject(MD_DATE_FORMATS) private _dateFormats: MdDateFormats) { + @Optional() @Inject(MD_DATE_FORMATS) private _dateFormats: MdDateFormats, + private _changeDetectorRef: ChangeDetectorRef) { if (!this._dateAdapter) { throw createMissingDateImplError('DateAdapter'); } @@ -136,7 +138,7 @@ export class MdMonthView implements AfterContentInit { } /** Initializes this month view. */ - private _init() { + _init() { this._selectedDate = this._getDateInCurrentMonth(this.selected); this._todayDate = this._getDateInCurrentMonth(this._dateAdapter.today()); this._monthLabel = @@ -150,6 +152,7 @@ export class MdMonthView implements AfterContentInit { this._dateAdapter.getFirstDayOfWeek()) % DAYS_PER_WEEK; this._createWeekCells(); + this._changeDetectorRef.markForCheck(); } /** Creates MdCalendarCells for the dates in this month. */ diff --git a/src/lib/datepicker/year-view.ts b/src/lib/datepicker/year-view.ts index db7699df0491..efbe91ff7d73 100644 --- a/src/lib/datepicker/year-view.ts +++ b/src/lib/datepicker/year-view.ts @@ -16,6 +16,7 @@ import { Optional, Output, ViewEncapsulation, + ChangeDetectorRef, } from '@angular/core'; import {DateAdapter, MD_DATE_FORMATS, MdDateFormats} from '@angular/material/core'; import {MdCalendarCell} from './calendar-body'; @@ -79,7 +80,8 @@ export class MdYearView implements AfterContentInit { _selectedMonth: number | null; constructor(@Optional() public _dateAdapter: DateAdapter, - @Optional() @Inject(MD_DATE_FORMATS) private _dateFormats: MdDateFormats) { + @Optional() @Inject(MD_DATE_FORMATS) private _dateFormats: MdDateFormats, + private _changeDetectorRef: ChangeDetectorRef) { if (!this._dateAdapter) { throw createMissingDateImplError('DateAdapter'); } @@ -104,7 +106,7 @@ export class MdYearView implements AfterContentInit { } /** Initializes this month view. */ - private _init() { + _init() { this._selectedMonth = this._getMonthInCurrentYear(this.selected); this._todayMonth = this._getMonthInCurrentYear(this._dateAdapter.today()); this._yearLabel = this._dateAdapter.getYearName(this.activeDate); @@ -113,6 +115,7 @@ export class MdYearView implements AfterContentInit { // First row of months only contains 5 elements so we can fit the year label on the same row. this._months = [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]].map(row => row.map( month => this._createCellForMonth(month, monthNames[month]))); + this._changeDetectorRef.markForCheck(); } /**