Skip to content

Commit 85993d3

Browse files
crisbetommalerba
authored andcommitted
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.
1 parent 9fe6386 commit 85993d3

File tree

5 files changed

+78
-16
lines changed

5 files changed

+78
-16
lines changed

src/demo-app/datepicker/datepicker-demo.html

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,26 @@ <h2>Options</h2>
44
<md-checkbox [(ngModel)]="filterOdd">Filter odd months and dates</md-checkbox>
55
<md-checkbox [(ngModel)]="yearView">Start in year view</md-checkbox>
66
<md-checkbox [(ngModel)]="datepickerDisabled">Disable datepicker</md-checkbox>
7+
<md-checkbox [(ngModel)]="inputDisabled">Disable input</md-checkbox>
78
</p>
89
<p>
910
<md-form-field>
10-
<input mdInput [mdDatepicker]="minDatePicker" [(ngModel)]="minDate" placeholder="Min date">
11+
<input mdInput [mdDatepicker]="minDatePicker" [(ngModel)]="minDate" placeholder="Min date"
12+
[disabled]="inputDisabled">
1113
<md-datepicker-toggle mdSuffix [for]="minDatePicker"></md-datepicker-toggle>
1214
<md-datepicker #minDatePicker [touchUi]="touch" [disabled]="datepickerDisabled"></md-datepicker>
1315
</md-form-field>
1416
<md-form-field>
15-
<input mdInput [mdDatepicker]="maxDatePicker" [(ngModel)]="maxDate" placeholder="Max date">
17+
<input mdInput [mdDatepicker]="maxDatePicker" [(ngModel)]="maxDate" placeholder="Max date"
18+
[disabled]="inputDisabled">
1619
<md-datepicker-toggle mdSuffix [for]="maxDatePicker"></md-datepicker-toggle>
1720
<md-datepicker #maxDatePicker [touchUi]="touch" [disabled]="datepickerDisabled"></md-datepicker>
1821
</md-form-field>
1922
</p>
2023
<p>
2124
<md-form-field>
22-
<input mdInput [mdDatepicker]="startAtPicker" [(ngModel)]="startAt" placeholder="Start at date">
25+
<input mdInput [mdDatepicker]="startAtPicker" [(ngModel)]="startAt" placeholder="Start at date"
26+
[disabled]="inputDisabled">
2327
<md-datepicker-toggle mdSuffix [for]="startAtPicker"></md-datepicker-toggle>
2428
<md-datepicker #startAtPicker [touchUi]="touch" [disabled]="datepickerDisabled"></md-datepicker>
2529
</md-form-field>
@@ -37,6 +41,7 @@ <h2>Result</h2>
3741
[min]="minDate"
3842
[max]="maxDate"
3943
[mdDatepickerFilter]="filterOdd ? dateFilter : null"
44+
[disabled]="inputDisabled"
4045
placeholder="Pick a date"
4146
(dateInput)="onDateInput($event)"
4247
(dateChange)="onDateChange($event)">
@@ -64,6 +69,7 @@ <h2>Result</h2>
6469
[(ngModel)]="date"
6570
[min]="minDate"
6671
[max]="maxDate"
72+
[disabled]="inputDisabled"
6773
[mdDatepickerFilter]="filterOdd ? dateFilter : null"
6874
placeholder="Pick a date">
6975
<md-datepicker-toggle [for]="resultPicker2"></md-datepicker-toggle>
@@ -81,7 +87,7 @@ <h2>Input disabled datepicker</h2>
8187
<md-datepicker-toggle [for]="datePicker1"></md-datepicker-toggle>
8288
<md-form-field>
8389
<input mdInput [mdDatepicker]="datePicker1" [(ngModel)]="date" [min]="minDate" [max]="maxDate"
84-
[mdDatepickerFilter]="filterOdd ? dateFilter : null" [disabled]="true"
90+
[mdDatepickerFilter]="filterOdd ? dateFilter : null" disabled
8591
placeholder="Input disabled">
8692
<md-datepicker #datePicker1 [touchUi]="touch" [startAt]="startAt"
8793
[startView]="yearView ? 'year' : 'month'"></md-datepicker>

src/lib/datepicker/datepicker-input.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,12 @@ export class MdDatepickerInput<D> implements AfterContentInit, ControlValueAcces
153153
@Input()
154154
get disabled() { return this._disabled; }
155155
set disabled(value: any) {
156-
this._disabled = coerceBooleanProperty(value);
156+
const newValue = coerceBooleanProperty(value);
157+
158+
if (this._disabled !== newValue) {
159+
this._disabled = newValue;
160+
this._disabledChange.emit(newValue);
161+
}
157162
}
158163
private _disabled: boolean;
159164

@@ -166,6 +171,9 @@ export class MdDatepickerInput<D> implements AfterContentInit, ControlValueAcces
166171
/** Emits when the value changes (either due to user input or programmatic change). */
167172
_valueChange = new EventEmitter<D|null>();
168173

174+
/** Emits when the disabled state has changed */
175+
_disabledChange = new EventEmitter<boolean>();
176+
169177
_onTouched = () => {};
170178

171179
private _cvaOnChange: (value: any) => void = () => {};
@@ -237,6 +245,8 @@ export class MdDatepickerInput<D> implements AfterContentInit, ControlValueAcces
237245

238246
ngOnDestroy() {
239247
this._datepickerSubscription.unsubscribe();
248+
this._valueChange.complete();
249+
this._disabledChange.complete();
240250
}
241251

242252
registerOnValidatorChange(fn: () => void): void {

src/lib/datepicker/datepicker-toggle.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@ import {
1212
Input,
1313
ViewEncapsulation,
1414
OnDestroy,
15+
OnChanges,
16+
SimpleChanges,
1517
ChangeDetectorRef,
1618
} from '@angular/core';
1719
import {MdDatepicker} from './datepicker';
1820
import {MdDatepickerIntl} from './datepicker-intl';
1921
import {coerceBooleanProperty} from '@angular/cdk/coercion';
2022
import {Subscription} from 'rxjs/Subscription';
23+
import {merge} from 'rxjs/observable/merge';
24+
import {of as observableOf} from 'rxjs/observable/of';
2125

2226

2327
@Component({
@@ -30,8 +34,8 @@ import {Subscription} from 'rxjs/Subscription';
3034
encapsulation: ViewEncapsulation.None,
3135
changeDetection: ChangeDetectionStrategy.OnPush,
3236
})
33-
export class MdDatepickerToggle<D> implements OnDestroy {
34-
private _intlChanges: Subscription;
37+
export class MdDatepickerToggle<D> implements OnChanges, OnDestroy {
38+
private _stateChanges = Subscription.EMPTY;
3539

3640
/** Datepicker instance that the button will toggle. */
3741
@Input('for') datepicker: MdDatepicker<D>;
@@ -46,12 +50,26 @@ export class MdDatepickerToggle<D> implements OnDestroy {
4650
}
4751
private _disabled: boolean;
4852

49-
constructor(public _intl: MdDatepickerIntl, changeDetectorRef: ChangeDetectorRef) {
50-
this._intlChanges = _intl.changes.subscribe(() => changeDetectorRef.markForCheck());
53+
constructor(
54+
public _intl: MdDatepickerIntl,
55+
private _changeDetectorRef: ChangeDetectorRef) {}
56+
57+
ngOnChanges(changes: SimpleChanges) {
58+
if (changes.datepicker) {
59+
const datepicker: MdDatepicker<D> = changes.datepicker.currentValue;
60+
const datepickerDisabled = datepicker ? datepicker._disabledChange : observableOf();
61+
const inputDisabled = datepicker && datepicker._datepickerInput ?
62+
datepicker._datepickerInput._disabledChange :
63+
observableOf();
64+
65+
this._stateChanges.unsubscribe();
66+
this._stateChanges = merge(this._intl.changes, datepickerDisabled, inputDisabled)
67+
.subscribe(() => this._changeDetectorRef.markForCheck());
68+
}
5169
}
5270

5371
ngOnDestroy() {
54-
this._intlChanges.unsubscribe();
72+
this._stateChanges.unsubscribe();
5573
}
5674

5775
_open(event: Event): void {

src/lib/datepicker/datepicker.spec.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -552,11 +552,28 @@ describe('MdDatepicker', () => {
552552
it('should not open calendar when toggle clicked if datepicker is disabled', () => {
553553
testComponent.datepicker.disabled = true;
554554
fixture.detectChanges();
555+
const toggle = fixture.debugElement.query(By.css('button')).nativeElement;
555556

557+
expect(toggle.hasAttribute('disabled')).toBe(true);
556558
expect(document.querySelector('md-dialog-container')).toBeNull();
557559

558-
let toggle = fixture.debugElement.query(By.css('button'));
559-
dispatchMouseEvent(toggle.nativeElement, 'click');
560+
dispatchMouseEvent(toggle, 'click');
561+
fixture.detectChanges();
562+
563+
expect(document.querySelector('md-dialog-container')).toBeNull();
564+
});
565+
566+
it('should not open calendar when toggle clicked if input is disabled', () => {
567+
expect(testComponent.datepicker.disabled).toBeUndefined();
568+
569+
testComponent.input.disabled = true;
570+
fixture.detectChanges();
571+
const toggle = fixture.debugElement.query(By.css('button')).nativeElement;
572+
573+
expect(toggle.hasAttribute('disabled')).toBe(true);
574+
expect(document.querySelector('md-dialog-container')).toBeNull();
575+
576+
dispatchMouseEvent(toggle, 'click');
560577
fixture.detectChanges();
561578

562579
expect(document.querySelector('md-dialog-container')).toBeNull();
@@ -1075,6 +1092,7 @@ class DatepickerWithFormControl {
10751092
})
10761093
class DatepickerWithToggle {
10771094
@ViewChild('d') datepicker: MdDatepicker<Date>;
1095+
@ViewChild(MdDatepickerInput) input: MdDatepickerInput<Date>;
10781096
touchUI = true;
10791097
}
10801098

src/lib/datepicker/datepicker.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {MdDialog} from '../dialog/dialog';
4444
import {MdDialogRef} from '../dialog/dialog-ref';
4545
import {MdDatepickerInput} from './datepicker-input';
4646
import {Subscription} from 'rxjs/Subscription';
47+
import {Subject} from 'rxjs/Subject';
4748
import {DateAdapter} from '../core/datetime/index';
4849
import {createMissingDateImplError} from './datepicker-errors';
4950
import {MdCalendar} from './calendar';
@@ -150,7 +151,12 @@ export class MdDatepicker<D> implements OnDestroy {
150151
return this._disabled === undefined ? this._datepickerInput.disabled : this._disabled;
151152
}
152153
set disabled(value: any) {
153-
this._disabled = coerceBooleanProperty(value);
154+
const newValue = coerceBooleanProperty(value);
155+
156+
if (newValue !== this._disabled) {
157+
this._disabled = newValue;
158+
this._disabledChange.next(newValue);
159+
}
154160
}
155161
private _disabled: boolean;
156162

@@ -194,14 +200,17 @@ export class MdDatepicker<D> implements OnDestroy {
194200
/** A portal containing the calendar for this datepicker. */
195201
private _calendarPortal: ComponentPortal<MdDatepickerContent<D>>;
196202

197-
/** The input element this datepicker is associated with. */
198-
private _datepickerInput: MdDatepickerInput<D>;
199-
200203
/** The element that was focused before the datepicker was opened. */
201204
private _focusedElementBeforeOpen: HTMLElement | null = null;
202205

203206
private _inputSubscription = Subscription.EMPTY;
204207

208+
/** The input element this datepicker is associated with. */
209+
_datepickerInput: MdDatepickerInput<D>;
210+
211+
/** Emits when the datepicker is disabled. */
212+
_disabledChange = new Subject<boolean>();
213+
205214
constructor(private _dialog: MdDialog,
206215
private _overlay: Overlay,
207216
private _ngZone: NgZone,
@@ -218,6 +227,7 @@ export class MdDatepicker<D> implements OnDestroy {
218227
ngOnDestroy() {
219228
this.close();
220229
this._inputSubscription.unsubscribe();
230+
this._disabledChange.complete();
221231

222232
if (this._popupRef) {
223233
this._popupRef.dispose();

0 commit comments

Comments
 (0)