Skip to content

Commit 35d2f12

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 04234f0 commit 35d2f12

File tree

2 files changed

+49
-13
lines changed

2 files changed

+49
-13
lines changed

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

Lines changed: 47 additions & 10 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, A} from '@angular/cdk/keycodes';
7-
import {CdkConnectedOverlay, OverlayModule, CdkOverlayOrigin} from './index';
8-
import {OverlayContainer} from './overlay-container';
7+
import {
8+
CdkConnectedOverlay,
9+
OverlayModule,
10+
CdkOverlayOrigin,
11+
ScrollDispatcher,
12+
Overlay,
13+
OverlayContainer,
14+
ScrollStrategy,
15+
} from './index';
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';
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

@@ -411,7 +424,7 @@ describe('Overlay directives', () => {
411424
});
412425

413426
describe('outputs', () => {
414-
it('should emit backdropClick appropriately', () => {
427+
it('should emit when the backdrop was clicked', () => {
415428
fixture.componentInstance.hasBackdrop = true;
416429
fixture.componentInstance.isOpen = true;
417430
fixture.detectChanges();
@@ -425,7 +438,7 @@ describe('Overlay directives', () => {
425438
.toHaveBeenCalledWith(jasmine.any(MouseEvent));
426439
});
427440

428-
it('should emit positionChange appropriately', () => {
441+
it('should emit when the position has changed', () => {
429442
expect(fixture.componentInstance.positionChangeHandler).not.toHaveBeenCalled();
430443
fixture.componentInstance.isOpen = true;
431444
fixture.detectChanges();
@@ -438,15 +451,24 @@ describe('Overlay directives', () => {
438451
.toBe(true, `Expected directive to emit an instance of ConnectedOverlayPositionChange.`);
439452
});
440453

441-
it('should emit attach and detach appropriately', () => {
454+
it('should emit when attached', () => {
442455
expect(fixture.componentInstance.attachHandler).not.toHaveBeenCalled();
443-
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
444456
fixture.componentInstance.isOpen = true;
445457
fixture.detectChanges();
446458

447459
expect(fixture.componentInstance.attachHandler).toHaveBeenCalled();
448460
expect(fixture.componentInstance.attachResult instanceof HTMLElement)
449461
.toBe(true, `Expected pane to be populated with HTML elements when attach was called.`);
462+
463+
fixture.componentInstance.isOpen = false;
464+
fixture.detectChanges();
465+
});
466+
467+
it('should emit when detached', () => {
468+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
469+
fixture.componentInstance.isOpen = true;
470+
fixture.detectChanges();
471+
450472
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
451473

452474
fixture.componentInstance.isOpen = false;
@@ -466,11 +488,24 @@ describe('Overlay directives', () => {
466488
expect(fixture.componentInstance.keydownHandler).toHaveBeenCalledWith(event);
467489
});
468490

491+
it('should emit when detached externally', inject([Overlay], (overlay: Overlay) => {
492+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
493+
fixture.componentInstance.scrollStrategy = overlay.scrollStrategies.close();
494+
fixture.componentInstance.isOpen = true;
495+
fixture.detectChanges();
496+
497+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
498+
499+
scrolledSubject.next();
500+
fixture.detectChanges();
501+
502+
expect(fixture.componentInstance.detachHandler).toHaveBeenCalled();
503+
}));
504+
469505
});
470506

471507
});
472508

473-
474509
@Component({
475510
template: `
476511
<button cdk-overlay-origin id="trigger" #trigger="cdkOverlayOrigin">Toggle menu</button>
@@ -486,6 +521,7 @@ describe('Overlay directives', () => {
486521
[cdkConnectedOverlayFlexibleDimensions]="flexibleDimensions"
487522
[cdkConnectedOverlayGrowAfterOpen]="growAfterOpen"
488523
[cdkConnectedOverlayPush]="push"
524+
[cdkConnectedOverlayScrollStrategy]="scrollStrategy"
489525
cdkConnectedOverlayBackdropClass="mat-test-class"
490526
cdkConnectedOverlayPanelClass="cdk-test-panel-class"
491527
(backdropClick)="backdropClickHandler($event)"
@@ -519,13 +555,14 @@ class ConnectedOverlayDirectiveTest {
519555
flexibleDimensions: boolean;
520556
growAfterOpen: boolean;
521557
push: boolean;
558+
scrollStrategy: ScrollStrategy;
522559
backdropClickHandler = jasmine.createSpy('backdropClick handler');
523560
positionChangeHandler = jasmine.createSpy('positionChange handler');
524561
keydownHandler = jasmine.createSpy('keydown handler');
525562
positionOverrides: ConnectionPositionPair[];
526563
attachHandler = jasmine.createSpy('attachHandler').and.callFake(() => {
527-
this.attachResult =
528-
this.connectedOverlayDirective.overlayRef.overlayElement.querySelector('p') as HTMLElement;
564+
const overlayElement = this.connectedOverlayDirective.overlayRef.overlayElement;
565+
this.attachResult = overlayElement.querySelector('p') as HTMLElement;
529566
});
530567
detachHandler = jasmine.createSpy('detachHandler');
531568
attachResult: HTMLElement;

src/cdk/overlay/overlay-directives.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,8 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
259259
}
260260

261261
this._overlayRef = this._overlay.create(this._buildConfig());
262-
262+
this._overlayRef.attachments().subscribe(() => this.attach.emit());
263+
this._overlayRef.detachments().subscribe(() => this.detach.emit());
263264
this._overlayRef.keydownEvents().subscribe((event: KeyboardEvent) => {
264265
this.overlayKeydown.next(event);
265266

@@ -353,7 +354,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
353354

354355
if (!this._overlayRef.hasAttached()) {
355356
this._overlayRef.attach(this._templatePortal);
356-
this.attach.emit();
357357
}
358358

359359
if (this.hasBackdrop) {
@@ -367,7 +367,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
367367
private _detachOverlay() {
368368
if (this._overlayRef) {
369369
this._overlayRef.detach();
370-
this.detach.emit();
371370
}
372371

373372
this._backdropSubscription.unsubscribe();

0 commit comments

Comments
 (0)