Skip to content

Commit b5672e7

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 76a6e7b commit b5672e7

File tree

2 files changed

+50
-15
lines changed

2 files changed

+50
-15
lines changed

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

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,26 @@ 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 {ConnectedOverlayDirective, OverlayModule, OverlayOrigin} from './index';
8-
import {OverlayContainer} from './overlay-container';
9-
import {ConnectedPositionStrategy} from './position/connected-position-strategy';
10-
import {ConnectedOverlayPositionChange} from './position/connected-position';
7+
import {
8+
ConnectedOverlayDirective,
9+
OverlayModule,
10+
OverlayOrigin,
11+
Overlay,
12+
ScrollStrategy,
13+
ScrollDispatcher,
14+
OverlayContainer,
15+
ConnectedPositionStrategy,
16+
ConnectedOverlayPositionChange,
17+
} from './index';
18+
import {Subject} from 'rxjs/Subject';
1119

1220

1321
describe('Overlay directives', () => {
1422
let overlayContainer: OverlayContainer;
1523
let overlayContainerElement: HTMLElement;
1624
let fixture: ComponentFixture<ConnectedOverlayDirectiveTest>;
1725
let dir: {value: string};
26+
let scrolledSubject = new Subject();
1827

1928
beforeEach(() => {
2029
TestBed.configureTestingModule({
@@ -23,7 +32,10 @@ describe('Overlay directives', () => {
2332
providers: [
2433
{provide: Directionality, useFactory: () => {
2534
return dir = {value: 'ltr'};
26-
}}
35+
}},
36+
{provide: ScrollDispatcher, useFactory: () => ({
37+
scrolled: () => scrolledSubject.asObservable()
38+
})}
2739
],
2840
});
2941
});
@@ -253,7 +265,7 @@ describe('Overlay directives', () => {
253265
});
254266

255267
describe('outputs', () => {
256-
it('should emit backdropClick appropriately', () => {
268+
it('should emit when the backdrop was clicked', () => {
257269
fixture.componentInstance.hasBackdrop = true;
258270
fixture.componentInstance.isOpen = true;
259271
fixture.detectChanges();
@@ -266,7 +278,7 @@ describe('Overlay directives', () => {
266278
expect(fixture.componentInstance.backdropClicked).toBe(true);
267279
});
268280

269-
it('should emit positionChange appropriately', () => {
281+
it('should emit when the position has changed', () => {
270282
expect(fixture.componentInstance.positionChangeHandler).not.toHaveBeenCalled();
271283
fixture.componentInstance.isOpen = true;
272284
fixture.detectChanges();
@@ -279,32 +291,54 @@ describe('Overlay directives', () => {
279291
.toBe(true, `Expected directive to emit an instance of ConnectedOverlayPositionChange.`);
280292
});
281293

282-
it('should emit attach and detach appropriately', () => {
294+
it('should emit when attached', () => {
283295
expect(fixture.componentInstance.attachHandler).not.toHaveBeenCalled();
284-
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
285296
fixture.componentInstance.isOpen = true;
286297
fixture.detectChanges();
287298

288299
expect(fixture.componentInstance.attachHandler).toHaveBeenCalled();
289300
expect(fixture.componentInstance.attachResult instanceof HTMLElement)
290301
.toBe(true, `Expected pane to be populated with HTML elements when attach was called.`);
302+
303+
fixture.componentInstance.isOpen = false;
304+
fixture.detectChanges();
305+
});
306+
307+
it('should emit when detached', () => {
308+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
309+
fixture.componentInstance.isOpen = true;
310+
fixture.detectChanges();
311+
291312
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
292313

293314
fixture.componentInstance.isOpen = false;
294315
fixture.detectChanges();
295316
expect(fixture.componentInstance.detachHandler).toHaveBeenCalled();
296317
});
297318

319+
it('should emit when detached externally', inject([Overlay], (overlay: Overlay) => {
320+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
321+
fixture.componentInstance.scrollStrategy = overlay.scrollStrategies.close();
322+
fixture.componentInstance.isOpen = true;
323+
fixture.detectChanges();
324+
325+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
326+
327+
scrolledSubject.next();
328+
fixture.detectChanges();
329+
330+
expect(fixture.componentInstance.detachHandler).toHaveBeenCalled();
331+
}));
332+
298333
});
299334

300335
});
301336

302-
303337
@Component({
304338
template: `
305339
<button cdk-overlay-origin #trigger="cdkOverlayOrigin">Toggle menu</button>
306340
<ng-template cdk-connected-overlay [open]="isOpen" [width]="width" [height]="height"
307-
[origin]="trigger"
341+
[origin]="trigger" [scrollStrategy]="scrollStrategy"
308342
[hasBackdrop]="hasBackdrop" backdropClass="mat-test-class"
309343
(backdropClick)="backdropClicked=true" [offsetX]="offsetX" [offsetY]="offsetY"
310344
(positionChange)="positionChangeHandler($event)" (attach)="attachHandler()"
@@ -322,10 +356,11 @@ class ConnectedOverlayDirectiveTest {
322356
offsetY: number = 0;
323357
hasBackdrop: boolean;
324358
backdropClicked = false;
359+
scrollStrategy: ScrollStrategy;
325360
positionChangeHandler = jasmine.createSpy('positionChangeHandler');
326361
attachHandler = jasmine.createSpy('attachHandler').and.callFake(() => {
327-
this.attachResult =
328-
this.connectedOverlayDirective.overlayRef.overlayElement.querySelector('p') as HTMLElement;
362+
const overlayElement = this.connectedOverlayDirective.overlayRef.overlayElement;
363+
this.attachResult = overlayElement.querySelector('p') as HTMLElement;
329364
});
330365
detachHandler = jasmine.createSpy('detachHandler');
331366
attachResult: HTMLElement;

src/cdk/overlay/overlay-directives.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,8 @@ export class ConnectedOverlayDirective implements OnDestroy, OnChanges {
268268
}
269269

270270
this._overlayRef = this._overlay.create(this._buildConfig());
271+
this._overlayRef.attachments().subscribe(() => this.attach.emit());
272+
this._overlayRef.detachments().subscribe(() => this.detach.emit());
271273
}
272274

273275
/** Builds the overlay config based on the directive's inputs */
@@ -342,7 +344,6 @@ export class ConnectedOverlayDirective implements OnDestroy, OnChanges {
342344

343345
if (!this._overlayRef.hasAttached()) {
344346
this._overlayRef.attach(this._templatePortal);
345-
this.attach.emit();
346347
}
347348

348349
if (this.hasBackdrop) {
@@ -356,7 +357,6 @@ export class ConnectedOverlayDirective implements OnDestroy, OnChanges {
356357
private _detachOverlay() {
357358
if (this._overlayRef) {
358359
this._overlayRef.detach();
359-
this.detach.emit();
360360
}
361361

362362
this._backdropSubscription.unsubscribe();

0 commit comments

Comments
 (0)