Skip to content

Commit 0f076fd

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 4c3e385 commit 0f076fd

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 {ConnectedPositionStrategy} from './position/connected-position-strategy';
1017
import {
1118
ConnectedOverlayPositionChange,
1219
ConnectionPositionPair,
1320
} from './position/connected-position';
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

@@ -349,7 +362,7 @@ describe('Overlay directives', () => {
349362
});
350363

351364
describe('outputs', () => {
352-
it('should emit backdropClick appropriately', () => {
365+
it('should emit when the backdrop was clicked', () => {
353366
fixture.componentInstance.hasBackdrop = true;
354367
fixture.componentInstance.isOpen = true;
355368
fixture.detectChanges();
@@ -363,7 +376,7 @@ describe('Overlay directives', () => {
363376
.toHaveBeenCalledWith(jasmine.any(MouseEvent));
364377
});
365378

366-
it('should emit positionChange appropriately', () => {
379+
it('should emit when the position has changed', () => {
367380
expect(fixture.componentInstance.positionChangeHandler).not.toHaveBeenCalled();
368381
fixture.componentInstance.isOpen = true;
369382
fixture.detectChanges();
@@ -376,34 +389,57 @@ describe('Overlay directives', () => {
376389
.toBe(true, `Expected directive to emit an instance of ConnectedOverlayPositionChange.`);
377390
});
378391

379-
it('should emit attach and detach appropriately', () => {
392+
it('should emit when attached', () => {
380393
expect(fixture.componentInstance.attachHandler).not.toHaveBeenCalled();
381-
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
382394
fixture.componentInstance.isOpen = true;
383395
fixture.detectChanges();
384396

385397
expect(fixture.componentInstance.attachHandler).toHaveBeenCalled();
386398
expect(fixture.componentInstance.attachResult instanceof HTMLElement)
387399
.toBe(true, `Expected pane to be populated with HTML elements when attach was called.`);
400+
401+
fixture.componentInstance.isOpen = false;
402+
fixture.detectChanges();
403+
});
404+
405+
it('should emit when detached', () => {
406+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
407+
fixture.componentInstance.isOpen = true;
408+
fixture.detectChanges();
409+
388410
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
389411

390412
fixture.componentInstance.isOpen = false;
391413
fixture.detectChanges();
392414
expect(fixture.componentInstance.detachHandler).toHaveBeenCalled();
393415
});
394416

417+
it('should emit when detached externally', inject([Overlay], (overlay: Overlay) => {
418+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
419+
fixture.componentInstance.scrollStrategy = overlay.scrollStrategies.close();
420+
fixture.componentInstance.isOpen = true;
421+
fixture.detectChanges();
422+
423+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
424+
425+
scrolledSubject.next();
426+
fixture.detectChanges();
427+
428+
expect(fixture.componentInstance.detachHandler).toHaveBeenCalled();
429+
}));
430+
395431
});
396432

397433
});
398434

399-
400435
@Component({
401436
template: `
402437
<button cdk-overlay-origin id="trigger" #trigger="cdkOverlayOrigin">Toggle menu</button>
403438
<button cdk-overlay-origin id="otherTrigger" #otherTrigger="cdkOverlayOrigin">Toggle menu</button>
404439
405440
<ng-template cdk-connected-overlay [open]="isOpen" [width]="width" [height]="height"
406441
[cdkConnectedOverlayOrigin]="triggerOverride || trigger"
442+
[scrollStrategy]="scrollStrategy"
407443
[hasBackdrop]="hasBackdrop" backdropClass="mat-test-class"
408444
(backdropClick)="backdropClickHandler($event)" [offsetX]="offsetX" [offsetY]="offsetY"
409445
(positionChange)="positionChangeHandler($event)" (attach)="attachHandler()"
@@ -426,12 +462,13 @@ class ConnectedOverlayDirectiveTest {
426462
offsetY = 0;
427463
triggerOverride: CdkOverlayOrigin;
428464
hasBackdrop: boolean;
465+
scrollStrategy: ScrollStrategy;
429466
backdropClickHandler = jasmine.createSpy('backdropClick handler');
430467
positionChangeHandler = jasmine.createSpy('positionChangeHandler');
431468
positionOverrides: ConnectionPositionPair[];
432469
attachHandler = jasmine.createSpy('attachHandler').and.callFake(() => {
433-
this.attachResult =
434-
this.connectedOverlayDirective.overlayRef.overlayElement.querySelector('p') as HTMLElement;
470+
const overlayElement = this.connectedOverlayDirective.overlayRef.overlayElement;
471+
this.attachResult = overlayElement.querySelector('p') as HTMLElement;
435472
});
436473
detachHandler = jasmine.createSpy('detachHandler');
437474
attachResult: HTMLElement;

src/cdk/overlay/overlay-directives.ts

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

329329
this._overlayRef = this._overlay.create(this._buildConfig());
330+
this._overlayRef.attachments().subscribe(() => this.attach.emit());
331+
this._overlayRef.detachments().subscribe(() => this.detach.emit());
330332
}
331333

332334
/** Builds the overlay config based on the directive's inputs */
@@ -409,7 +411,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
409411

410412
if (!this._overlayRef.hasAttached()) {
411413
this._overlayRef.attach(this._templatePortal);
412-
this.attach.emit();
413414
}
414415

415416
if (this.hasBackdrop) {
@@ -423,7 +424,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
423424
private _detachOverlay() {
424425
if (this._overlayRef) {
425426
this._overlayRef.detach();
426-
this.detach.emit();
427427
}
428428

429429
this._backdropSubscription.unsubscribe();

0 commit comments

Comments
 (0)