From 329962821b5aee2c524b1003bd8757cac52a708e Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Tue, 7 Aug 2018 22:36:59 +0200 Subject: [PATCH] fix(datepicker): input not picking up changes if datepicker is assigned after init (#12546) --- src/lib/datepicker/datepicker-input.ts | 41 +++++++-------- src/lib/datepicker/datepicker.spec.ts | 70 +++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 26 deletions(-) diff --git a/src/lib/datepicker/datepicker-input.ts b/src/lib/datepicker/datepicker-input.ts index ca77137b0cc..c491d6fdcb8 100644 --- a/src/lib/datepicker/datepicker-input.ts +++ b/src/lib/datepicker/datepicker-input.ts @@ -9,7 +9,6 @@ import {coerceBooleanProperty} from '@angular/cdk/coercion'; import {DOWN_ARROW} from '@angular/cdk/keycodes'; import { - AfterContentInit, Directive, ElementRef, EventEmitter, @@ -92,22 +91,28 @@ export class MatDatepickerInputEvent { }, exportAs: 'matDatepickerInput', }) -export class MatDatepickerInput implements AfterContentInit, ControlValueAccessor, OnDestroy, - Validator { +export class MatDatepickerInput implements ControlValueAccessor, OnDestroy, Validator { /** The datepicker that this input is associated with. */ @Input() set matDatepicker(value: MatDatepicker) { - this.registerDatepicker(value); + if (!value) { + return; + } + + this._datepicker = value; + this._datepicker._registerInput(this); + this._datepickerSubscription.unsubscribe(); + + this._datepickerSubscription = this._datepicker._selectedChanged.subscribe((selected: D) => { + this.value = selected; + this._cvaOnChange(selected); + this._onTouched(); + this.dateInput.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement)); + this.dateChange.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement)); + }); } _datepicker: MatDatepicker; - private registerDatepicker(value: MatDatepicker) { - if (value) { - this._datepicker = value; - this._datepicker._registerInput(this); - } - } - /** Function that can be used to filter out dates within the datepicker. */ @Input() set matDatepickerFilter(value: (date: D | null) => boolean) { @@ -252,18 +257,6 @@ export class MatDatepickerInput implements AfterContentInit, ControlValueAcce }); } - ngAfterContentInit() { - if (this._datepicker) { - this._datepickerSubscription = this._datepicker._selectedChanged.subscribe((selected: D) => { - this.value = selected; - this._cvaOnChange(selected); - this._onTouched(); - this.dateInput.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement)); - this.dateChange.emit(new MatDatepickerInputEvent(this, this._elementRef.nativeElement)); - }); - } - } - ngOnDestroy() { this._datepickerSubscription.unsubscribe(); this._localeSubscription.unsubscribe(); @@ -318,7 +311,7 @@ export class MatDatepickerInput implements AfterContentInit, ControlValueAcce } _onKeydown(event: KeyboardEvent) { - if (event.altKey && event.keyCode === DOWN_ARROW) { + if (this._datepicker && event.altKey && event.keyCode === DOWN_ARROW) { this._datepicker.open(); event.preventDefault(); } diff --git a/src/lib/datepicker/datepicker.spec.ts b/src/lib/datepicker/datepicker.spec.ts index 1e8b264b0ed..6b906f6a1fd 100644 --- a/src/lib/datepicker/datepicker.spec.ts +++ b/src/lib/datepicker/datepicker.spec.ts @@ -1,4 +1,4 @@ -import {ENTER, ESCAPE, RIGHT_ARROW, UP_ARROW} from '@angular/cdk/keycodes'; +import {ENTER, ESCAPE, RIGHT_ARROW, UP_ARROW, DOWN_ARROW} from '@angular/cdk/keycodes'; import {Overlay, OverlayContainer, ScrollDispatcher} from '@angular/cdk/overlay'; import { createKeyboardEvent, @@ -432,15 +432,66 @@ describe('MatDatepicker', () => { expect(testComponent.datepicker.opened).toBe(false); })); + it('should open the datpeicker using ALT + DOWN_ARROW', fakeAsync(() => { + expect(testComponent.datepicker.opened).toBe(false); + + const event = createKeyboardEvent('keydown', DOWN_ARROW); + Object.defineProperty(event, 'altKey', {get: () => true}); + + dispatchEvent(fixture.nativeElement.querySelector('input'), event); + fixture.detectChanges(); + flush(); + + expect(testComponent.datepicker.opened).toBe(true); + expect(event.defaultPrevented).toBe(true); + })); + }); describe('datepicker with too many inputs', () => { it('should throw when multiple inputs registered', fakeAsync(() => { - let fixture = createComponent(MultiInputDatepicker, [MatNativeDateModule]); + const fixture = createComponent(MultiInputDatepicker, [MatNativeDateModule]); expect(() => fixture.detectChanges()).toThrow(); })); }); + describe('datepicker that is assigned to input at a later point', () => { + it('should not throw on ALT + DOWN_ARROW for input without datepicker', fakeAsync(() => { + const fixture = createComponent(DelayedDatepicker, [MatNativeDateModule]); + fixture.detectChanges(); + + expect(() => { + const event = createKeyboardEvent('keydown', DOWN_ARROW); + Object.defineProperty(event, 'altKey', {get: () => true}); + dispatchEvent(fixture.nativeElement.querySelector('input'), event); + fixture.detectChanges(); + flush(); + }).not.toThrow(); + })); + + it('should handle value changes when a datepicker is assigned after init', fakeAsync(() => { + const fixture = createComponent(DelayedDatepicker, [MatNativeDateModule]); + const testComponent: DelayedDatepicker = fixture.componentInstance; + const toSelect = new Date(2017, JAN, 1); + + fixture.detectChanges(); + + expect(testComponent.datepickerInput.value).toBeNull(); + expect(testComponent.datepicker._selected).toBeNull(); + + testComponent.assignedDatepicker = testComponent.datepicker; + fixture.detectChanges(); + + testComponent.assignedDatepicker.select(toSelect); + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + + expect(testComponent.datepickerInput.value).toEqual(toSelect); + expect(testComponent.datepicker._selected).toEqual(toSelect); + })); + }); + describe('datepicker with no inputs', () => { let fixture: ComponentFixture; let testComponent: NoInputDatepicker; @@ -1787,3 +1838,18 @@ class DatepickerWithCustomHeader { `, }) class CustomHeaderForDatepicker {} + + + +@Component({ + template: ` + + + `, +}) +class DelayedDatepicker { + @ViewChild('d') datepicker: MatDatepicker; + @ViewChild(MatDatepickerInput) datepickerInput: MatDatepickerInput; + date: Date | null; + assignedDatepicker: MatDatepicker; +}