From 85993d3a96150a57ddfc5d87d0e28fe463de615e Mon Sep 17 00:00:00 2001
From: Kristiyan Kostadinov
Date: Tue, 12 Sep 2017 02:20:30 +0200
Subject: [PATCH] fix(datepicker): toggle not reacting to disabled state
changes in datepicker or input (#6964)
Fixes the datepicker toggle not reacting to changes in the disabled state of the associated datepicker and datepicker input.
---
src/demo-app/datepicker/datepicker-demo.html | 14 +++++++---
src/lib/datepicker/datepicker-input.ts | 12 ++++++++-
src/lib/datepicker/datepicker-toggle.ts | 28 ++++++++++++++++----
src/lib/datepicker/datepicker.spec.ts | 22 +++++++++++++--
src/lib/datepicker/datepicker.ts | 18 ++++++++++---
5 files changed, 78 insertions(+), 16 deletions(-)
diff --git a/src/demo-app/datepicker/datepicker-demo.html b/src/demo-app/datepicker/datepicker-demo.html
index 3cdf0a807e1..9f55b0820c6 100644
--- a/src/demo-app/datepicker/datepicker-demo.html
+++ b/src/demo-app/datepicker/datepicker-demo.html
@@ -4,22 +4,26 @@
Filter odd months and dates
Start in year view
Disable datepicker
+ Disable input
-
+
-
+
-
+
@@ -37,6 +41,7 @@
[min]="minDate"
[max]="maxDate"
[mdDatepickerFilter]="filterOdd ? dateFilter : null"
+ [disabled]="inputDisabled"
placeholder="Pick a date"
(dateInput)="onDateInput($event)"
(dateChange)="onDateChange($event)">
@@ -64,6 +69,7 @@
[(ngModel)]="date"
[min]="minDate"
[max]="maxDate"
+ [disabled]="inputDisabled"
[mdDatepickerFilter]="filterOdd ? dateFilter : null"
placeholder="Pick a date">
@@ -81,7 +87,7 @@
diff --git a/src/lib/datepicker/datepicker-input.ts b/src/lib/datepicker/datepicker-input.ts
index 8c0571d27f7..7847a51b0bb 100644
--- a/src/lib/datepicker/datepicker-input.ts
+++ b/src/lib/datepicker/datepicker-input.ts
@@ -153,7 +153,12 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces
@Input()
get disabled() { return this._disabled; }
set disabled(value: any) {
- this._disabled = coerceBooleanProperty(value);
+ const newValue = coerceBooleanProperty(value);
+
+ if (this._disabled !== newValue) {
+ this._disabled = newValue;
+ this._disabledChange.emit(newValue);
+ }
}
private _disabled: boolean;
@@ -166,6 +171,9 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces
/** Emits when the value changes (either due to user input or programmatic change). */
_valueChange = new EventEmitter();
+ /** Emits when the disabled state has changed */
+ _disabledChange = new EventEmitter();
+
_onTouched = () => {};
private _cvaOnChange: (value: any) => void = () => {};
@@ -237,6 +245,8 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces
ngOnDestroy() {
this._datepickerSubscription.unsubscribe();
+ this._valueChange.complete();
+ this._disabledChange.complete();
}
registerOnValidatorChange(fn: () => void): void {
diff --git a/src/lib/datepicker/datepicker-toggle.ts b/src/lib/datepicker/datepicker-toggle.ts
index 78c19ec890e..44698e2fbd1 100644
--- a/src/lib/datepicker/datepicker-toggle.ts
+++ b/src/lib/datepicker/datepicker-toggle.ts
@@ -12,12 +12,16 @@ import {
Input,
ViewEncapsulation,
OnDestroy,
+ OnChanges,
+ SimpleChanges,
ChangeDetectorRef,
} from '@angular/core';
import {MdDatepicker} from './datepicker';
import {MdDatepickerIntl} from './datepicker-intl';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {Subscription} from 'rxjs/Subscription';
+import {merge} from 'rxjs/observable/merge';
+import {of as observableOf} from 'rxjs/observable/of';
@Component({
@@ -30,8 +34,8 @@ import {Subscription} from 'rxjs/Subscription';
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class MdDatepickerToggle implements OnDestroy {
- private _intlChanges: Subscription;
+export class MdDatepickerToggle implements OnChanges, OnDestroy {
+ private _stateChanges = Subscription.EMPTY;
/** Datepicker instance that the button will toggle. */
@Input('for') datepicker: MdDatepicker;
@@ -46,12 +50,26 @@ export class MdDatepickerToggle implements OnDestroy {
}
private _disabled: boolean;
- constructor(public _intl: MdDatepickerIntl, changeDetectorRef: ChangeDetectorRef) {
- this._intlChanges = _intl.changes.subscribe(() => changeDetectorRef.markForCheck());
+ constructor(
+ public _intl: MdDatepickerIntl,
+ private _changeDetectorRef: ChangeDetectorRef) {}
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes.datepicker) {
+ const datepicker: MdDatepicker = changes.datepicker.currentValue;
+ const datepickerDisabled = datepicker ? datepicker._disabledChange : observableOf();
+ const inputDisabled = datepicker && datepicker._datepickerInput ?
+ datepicker._datepickerInput._disabledChange :
+ observableOf();
+
+ this._stateChanges.unsubscribe();
+ this._stateChanges = merge(this._intl.changes, datepickerDisabled, inputDisabled)
+ .subscribe(() => this._changeDetectorRef.markForCheck());
+ }
}
ngOnDestroy() {
- this._intlChanges.unsubscribe();
+ this._stateChanges.unsubscribe();
}
_open(event: Event): void {
diff --git a/src/lib/datepicker/datepicker.spec.ts b/src/lib/datepicker/datepicker.spec.ts
index 631adefbb19..02158c0bbc6 100644
--- a/src/lib/datepicker/datepicker.spec.ts
+++ b/src/lib/datepicker/datepicker.spec.ts
@@ -552,11 +552,28 @@ describe('MdDatepicker', () => {
it('should not open calendar when toggle clicked if datepicker is disabled', () => {
testComponent.datepicker.disabled = true;
fixture.detectChanges();
+ const toggle = fixture.debugElement.query(By.css('button')).nativeElement;
+ expect(toggle.hasAttribute('disabled')).toBe(true);
expect(document.querySelector('md-dialog-container')).toBeNull();
- let toggle = fixture.debugElement.query(By.css('button'));
- dispatchMouseEvent(toggle.nativeElement, 'click');
+ dispatchMouseEvent(toggle, 'click');
+ fixture.detectChanges();
+
+ expect(document.querySelector('md-dialog-container')).toBeNull();
+ });
+
+ it('should not open calendar when toggle clicked if input is disabled', () => {
+ expect(testComponent.datepicker.disabled).toBeUndefined();
+
+ testComponent.input.disabled = true;
+ fixture.detectChanges();
+ const toggle = fixture.debugElement.query(By.css('button')).nativeElement;
+
+ expect(toggle.hasAttribute('disabled')).toBe(true);
+ expect(document.querySelector('md-dialog-container')).toBeNull();
+
+ dispatchMouseEvent(toggle, 'click');
fixture.detectChanges();
expect(document.querySelector('md-dialog-container')).toBeNull();
@@ -1075,6 +1092,7 @@ class DatepickerWithFormControl {
})
class DatepickerWithToggle {
@ViewChild('d') datepicker: MdDatepicker;
+ @ViewChild(MdDatepickerInput) input: MdDatepickerInput;
touchUI = true;
}
diff --git a/src/lib/datepicker/datepicker.ts b/src/lib/datepicker/datepicker.ts
index 7f8ed7d49bf..6e87b996dee 100644
--- a/src/lib/datepicker/datepicker.ts
+++ b/src/lib/datepicker/datepicker.ts
@@ -44,6 +44,7 @@ import {MdDialog} from '../dialog/dialog';
import {MdDialogRef} from '../dialog/dialog-ref';
import {MdDatepickerInput} from './datepicker-input';
import {Subscription} from 'rxjs/Subscription';
+import {Subject} from 'rxjs/Subject';
import {DateAdapter} from '../core/datetime/index';
import {createMissingDateImplError} from './datepicker-errors';
import {MdCalendar} from './calendar';
@@ -150,7 +151,12 @@ export class MdDatepicker implements OnDestroy {
return this._disabled === undefined ? this._datepickerInput.disabled : this._disabled;
}
set disabled(value: any) {
- this._disabled = coerceBooleanProperty(value);
+ const newValue = coerceBooleanProperty(value);
+
+ if (newValue !== this._disabled) {
+ this._disabled = newValue;
+ this._disabledChange.next(newValue);
+ }
}
private _disabled: boolean;
@@ -194,14 +200,17 @@ export class MdDatepicker implements OnDestroy {
/** A portal containing the calendar for this datepicker. */
private _calendarPortal: ComponentPortal>;
- /** The input element this datepicker is associated with. */
- private _datepickerInput: MdDatepickerInput;
-
/** The element that was focused before the datepicker was opened. */
private _focusedElementBeforeOpen: HTMLElement | null = null;
private _inputSubscription = Subscription.EMPTY;
+ /** The input element this datepicker is associated with. */
+ _datepickerInput: MdDatepickerInput;
+
+ /** Emits when the datepicker is disabled. */
+ _disabledChange = new Subject();
+
constructor(private _dialog: MdDialog,
private _overlay: Overlay,
private _ngZone: NgZone,
@@ -218,6 +227,7 @@ export class MdDatepicker implements OnDestroy {
ngOnDestroy() {
this.close();
this._inputSubscription.unsubscribe();
+ this._disabledChange.complete();
if (this._popupRef) {
this._popupRef.dispose();