Skip to content

Commit 353df7a

Browse files
committed
fix(overlay): overlay directives not emitting when detached externally
Currently the `ConnectedOverlayDirective` only emits the `detach` event when it _thinks_ that the overlay is detached (escape press, backdrop click etc.), but this won't necessarily be correct (e.g. when it was closed by a scroll strategy). These changes refactor the outputs to always be one-to-one with the `OverlayRef` detachments.
1 parent c28549d commit 353df7a

File tree

2 files changed

+48
-11
lines changed

2 files changed

+48
-11
lines changed

src/cdk/overlay/overlay-directives.spec.ts

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,39 @@ import {ComponentFixture, TestBed, async, inject} from '@angular/core/testing';
44
import {Directionality} from '@angular/cdk/bidi';
55
import {dispatchKeyboardEvent} from '@angular/cdk/testing';
66
import {ESCAPE} from '@angular/cdk/keycodes';
7-
import {CdkConnectedOverlay, OverlayModule, CdkOverlayOrigin} from './index';
7+
import {
8+
CdkConnectedOverlay,
9+
OverlayModule,
10+
CdkOverlayOrigin,
11+
ScrollDispatcher,
12+
Overlay,
13+
ScrollStrategy,
14+
} from './index';
815
import {OverlayContainer} from './overlay-container';
916
import {
1017
ConnectedOverlayPositionChange,
1118
ConnectionPositionPair,
1219
} from './position/connected-position';
1320
import {FlexibleConnectedPositionStrategy} from './position/flexible-connected-position-strategy';
21+
import {Subject} from 'rxjs/Subject';
1422

1523

1624
describe('Overlay directives', () => {
1725
let overlayContainer: OverlayContainer;
1826
let overlayContainerElement: HTMLElement;
1927
let fixture: ComponentFixture<ConnectedOverlayDirectiveTest>;
2028
let dir: {value: string};
29+
let scrolledSubject = new Subject();
2130

2231
beforeEach(() => {
2332
TestBed.configureTestingModule({
2433
imports: [OverlayModule],
2534
declarations: [ConnectedOverlayDirectiveTest, ConnectedOverlayPropertyInitOrder],
26-
providers: [{provide: Directionality, useFactory: () => dir = {value: 'ltr'}}],
35+
providers: [{provide: Directionality, useFactory: () => dir = {value: 'ltr'}},
36+
{provide: ScrollDispatcher, useFactory: () => ({
37+
scrolled: () => scrolledSubject.asObservable()
38+
})}
39+
],
2740
});
2841
});
2942

@@ -336,7 +349,7 @@ describe('Overlay directives', () => {
336349
});
337350

338351
describe('outputs', () => {
339-
it('should emit backdropClick appropriately', () => {
352+
it('should emit when the backdrop was clicked', () => {
340353
fixture.componentInstance.hasBackdrop = true;
341354
fixture.componentInstance.isOpen = true;
342355
fixture.detectChanges();
@@ -350,7 +363,7 @@ describe('Overlay directives', () => {
350363
.toHaveBeenCalledWith(jasmine.any(MouseEvent));
351364
});
352365

353-
it('should emit positionChange appropriately', () => {
366+
it('should emit when the position has changed', () => {
354367
expect(fixture.componentInstance.positionChangeHandler).not.toHaveBeenCalled();
355368
fixture.componentInstance.isOpen = true;
356369
fixture.detectChanges();
@@ -363,27 +376,49 @@ describe('Overlay directives', () => {
363376
.toBe(true, `Expected directive to emit an instance of ConnectedOverlayPositionChange.`);
364377
});
365378

366-
it('should emit attach and detach appropriately', () => {
379+
it('should emit when attached', () => {
367380
expect(fixture.componentInstance.attachHandler).not.toHaveBeenCalled();
368-
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
369381
fixture.componentInstance.isOpen = true;
370382
fixture.detectChanges();
371383

372384
expect(fixture.componentInstance.attachHandler).toHaveBeenCalled();
373385
expect(fixture.componentInstance.attachResult instanceof HTMLElement)
374386
.toBe(true, `Expected pane to be populated with HTML elements when attach was called.`);
387+
388+
fixture.componentInstance.isOpen = false;
389+
fixture.detectChanges();
390+
});
391+
392+
it('should emit when detached', () => {
393+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
394+
fixture.componentInstance.isOpen = true;
395+
fixture.detectChanges();
396+
375397
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
376398

377399
fixture.componentInstance.isOpen = false;
378400
fixture.detectChanges();
379401
expect(fixture.componentInstance.detachHandler).toHaveBeenCalled();
380402
});
381403

404+
it('should emit when detached externally', inject([Overlay], (overlay: Overlay) => {
405+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
406+
fixture.componentInstance.scrollStrategy = overlay.scrollStrategies.close();
407+
fixture.componentInstance.isOpen = true;
408+
fixture.detectChanges();
409+
410+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
411+
412+
scrolledSubject.next();
413+
fixture.detectChanges();
414+
415+
expect(fixture.componentInstance.detachHandler).toHaveBeenCalled();
416+
}));
417+
382418
});
383419

384420
});
385421

386-
387422
@Component({
388423
template: `
389424
<button cdk-overlay-origin id="trigger" #trigger="cdkOverlayOrigin">Toggle menu</button>
@@ -395,6 +430,7 @@ describe('Overlay directives', () => {
395430
[cdkConnectedOverlayHeight]="height"
396431
[cdkConnectedOverlayOrigin]="triggerOverride || trigger"
397432
[cdkConnectedOverlayHasBackdrop]="hasBackdrop"
433+
[cdkConnectedOverlayScrollStrategy]="scrollStrategy"
398434
cdkConnectedOverlayBackdropClass="mat-test-class"
399435
(backdropClick)="backdropClickHandler($event)"
400436
[cdkConnectedOverlayOffsetX]="offsetX"
@@ -422,12 +458,13 @@ class ConnectedOverlayDirectiveTest {
422458
offsetY = 0;
423459
triggerOverride: CdkOverlayOrigin;
424460
hasBackdrop: boolean;
461+
scrollStrategy: ScrollStrategy;
425462
backdropClickHandler = jasmine.createSpy('backdropClick handler');
426463
positionChangeHandler = jasmine.createSpy('positionChangeHandler');
427464
positionOverrides: ConnectionPositionPair[];
428465
attachHandler = jasmine.createSpy('attachHandler').and.callFake(() => {
429-
this.attachResult =
430-
this.connectedOverlayDirective.overlayRef.overlayElement.querySelector('p') as HTMLElement;
466+
const overlayElement = this.connectedOverlayDirective.overlayRef.overlayElement;
467+
this.attachResult = overlayElement.querySelector('p') as HTMLElement;
431468
});
432469
detachHandler = jasmine.createSpy('detachHandler');
433470
attachResult: HTMLElement;

src/cdk/overlay/overlay-directives.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
243243
}
244244

245245
this._overlayRef = this._overlay.create(this._buildConfig());
246+
this._overlayRef.attachments().subscribe(() => this.attach.emit());
247+
this._overlayRef.detachments().subscribe(() => this.detach.emit());
246248
}
247249

248250
/** Builds the overlay config based on the directive's inputs */
@@ -338,7 +340,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
338340

339341
if (!this._overlayRef.hasAttached()) {
340342
this._overlayRef.attach(this._templatePortal);
341-
this.attach.emit();
342343
}
343344

344345
if (this.hasBackdrop) {
@@ -352,7 +353,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
352353
private _detachOverlay() {
353354
if (this._overlayRef) {
354355
this._overlayRef.detach();
355-
this.detach.emit();
356356
}
357357

358358
this._backdropSubscription.unsubscribe();

0 commit comments

Comments
 (0)