Skip to content

Commit a0bd162

Browse files
crisbetojelbourn
authored andcommitted
fix(datepicker): not resetting when detached externally (#9133)
Fixes not being able to reopen a datepicker if it's detached via the `OverlayRef` (e.g. by a `CloseScrollStrategy`).
1 parent b0449ab commit a0bd162

File tree

2 files changed

+55
-9
lines changed

2 files changed

+55
-9
lines changed

src/lib/datepicker/datepicker.spec.ts

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {ENTER, ESCAPE, RIGHT_ARROW} from '@angular/cdk/keycodes';
2-
import {OverlayContainer} from '@angular/cdk/overlay';
2+
import {OverlayContainer, Overlay, ScrollDispatcher} from '@angular/cdk/overlay';
33
import {
44
createKeyboardEvent,
55
dispatchEvent,
@@ -27,7 +27,8 @@ import {MatInputModule} from '../input/index';
2727
import {MatDatepicker} from './datepicker';
2828
import {MatDatepickerInput} from './datepicker-input';
2929
import {MatDatepickerToggle} from './datepicker-toggle';
30-
import {MatDatepickerIntl, MatDatepickerModule} from './index';
30+
import {MatDatepickerIntl, MatDatepickerModule, MAT_DATEPICKER_SCROLL_STRATEGY} from './index';
31+
import {Subject} from 'rxjs/Subject';
3132

3233
describe('MatDatepicker', () => {
3334
const SUPPORTS_INTL = typeof Intl != 'undefined';
@@ -342,17 +343,55 @@ describe('MatDatepicker', () => {
342343
testComponent.datepicker.open();
343344
fixture.detectChanges();
344345

345-
spyOn(testComponent.datepicker, 'close').and.callThrough();
346-
346+
const spy = jasmine.createSpy('close event spy');
347+
const subscription = testComponent.datepicker.closedStream.subscribe(spy);
347348
const backdrop = document.querySelector('.cdk-overlay-backdrop')! as HTMLElement;
348349

349350
backdrop.click();
350351
fixture.detectChanges();
351352
flush();
352353

353-
expect(testComponent.datepicker.close).toHaveBeenCalledTimes(1);
354+
expect(spy).toHaveBeenCalledTimes(1);
354355
expect(testComponent.datepicker.opened).toBe(false);
356+
subscription.unsubscribe();
355357
}));
358+
359+
it('should reset the datepicker when it is closed externally',
360+
fakeAsync(inject([OverlayContainer], (oldOverlayContainer: OverlayContainer) => {
361+
362+
// Destroy the old container manually since resetting the testing module won't do it.
363+
oldOverlayContainer.ngOnDestroy();
364+
TestBed.resetTestingModule();
365+
366+
const scrolledSubject = new Subject();
367+
368+
// Stub out a `CloseScrollStrategy` so we can trigger a detachment via the `OverlayRef`.
369+
fixture = createComponent(StandardDatepicker, [MatNativeDateModule], [
370+
{
371+
provide: ScrollDispatcher,
372+
useValue: {scrolled: () => scrolledSubject}
373+
},
374+
{
375+
provide: MAT_DATEPICKER_SCROLL_STRATEGY,
376+
deps: [Overlay],
377+
useFactory: (overlay: Overlay) => () => overlay.scrollStrategies.close()
378+
}
379+
]);
380+
381+
fixture.detectChanges();
382+
testComponent = fixture.componentInstance;
383+
384+
testComponent.datepicker.open();
385+
fixture.detectChanges();
386+
387+
expect(testComponent.datepicker.opened).toBe(true);
388+
389+
scrolledSubject.next();
390+
flush();
391+
fixture.detectChanges();
392+
393+
expect(testComponent.datepicker.opened).toBe(false);
394+
})));
356395
});
357396

358397
describe('datepicker with too many inputs', () => {

src/lib/datepicker/datepicker.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {MatDialog, MatDialogRef} from '@angular/material/dialog';
4141
import {DOCUMENT} from '@angular/common';
4242
import {Subject} from 'rxjs/Subject';
4343
import {Subscription} from 'rxjs/Subscription';
44+
import {merge} from 'rxjs/observable/merge';
4445
import {MatCalendar} from './calendar';
4546
import {createMissingDateImplError} from './datepicker-errors';
4647
import {MatDatepickerInput} from './datepicker-input';
@@ -312,9 +313,13 @@ export class MatDatepicker<D> implements OnDestroy {
312313
}
313314

314315
const completeClose = () => {
315-
this._opened = false;
316-
this.closedStream.emit();
317-
this._focusedElementBeforeOpen = null;
316+
// The `_opened` could've been reset already if
317+
// we got two events in quick succession.
318+
if (this._opened) {
319+
this._opened = false;
320+
this.closedStream.emit();
321+
this._focusedElementBeforeOpen = null;
322+
}
318323
};
319324

320325
if (this._focusedElementBeforeOpen &&
@@ -376,7 +381,9 @@ export class MatDatepicker<D> implements OnDestroy {
376381
});
377382

378383
this._popupRef = this._overlay.create(overlayConfig);
379-
this._popupRef.backdropClick().subscribe(() => this.close());
384+
385+
merge(this._popupRef.backdropClick(), this._popupRef.detachments())
386+
.subscribe(() => this.close());
380387
}
381388

382389
/** Create the popup PositionStrategy. */

0 commit comments

Comments
 (0)