Skip to content

Commit 698ccb9

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 37086fd commit 698ccb9

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, fakeAsync, tick} from '@angula
44
import {Directionality} from '@angular/cdk/bidi';
55
import {dispatchKeyboardEvent, createKeyboardEvent, dispatchEvent} 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

@@ -479,7 +492,7 @@ describe('Overlay directives', () => {
479492
});
480493

481494
describe('outputs', () => {
482-
it('should emit backdropClick appropriately', () => {
495+
it('should emit when the backdrop was clicked', () => {
483496
fixture.componentInstance.hasBackdrop = true;
484497
fixture.componentInstance.isOpen = true;
485498
fixture.detectChanges();
@@ -493,7 +506,7 @@ describe('Overlay directives', () => {
493506
.toHaveBeenCalledWith(jasmine.any(MouseEvent));
494507
});
495508

496-
it('should emit positionChange appropriately', () => {
509+
it('should emit when the position has changed', () => {
497510
expect(fixture.componentInstance.positionChangeHandler).not.toHaveBeenCalled();
498511
fixture.componentInstance.isOpen = true;
499512
fixture.detectChanges();
@@ -506,15 +519,24 @@ describe('Overlay directives', () => {
506519
.toBe(true, `Expected directive to emit an instance of ConnectedOverlayPositionChange.`);
507520
});
508521

509-
it('should emit attach and detach appropriately', () => {
522+
it('should emit when attached', () => {
510523
expect(fixture.componentInstance.attachHandler).not.toHaveBeenCalled();
511-
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
512524
fixture.componentInstance.isOpen = true;
513525
fixture.detectChanges();
514526

515527
expect(fixture.componentInstance.attachHandler).toHaveBeenCalled();
516528
expect(fixture.componentInstance.attachResult instanceof HTMLElement)
517529
.toBe(true, `Expected pane to be populated with HTML elements when attach was called.`);
530+
531+
fixture.componentInstance.isOpen = false;
532+
fixture.detectChanges();
533+
});
534+
535+
it('should emit when detached', () => {
536+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
537+
fixture.componentInstance.isOpen = true;
538+
fixture.detectChanges();
539+
518540
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
519541

520542
fixture.componentInstance.isOpen = false;
@@ -534,11 +556,24 @@ describe('Overlay directives', () => {
534556
expect(fixture.componentInstance.keydownHandler).toHaveBeenCalledWith(event);
535557
});
536558

559+
it('should emit when detached externally', inject([Overlay], (overlay: Overlay) => {
560+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
561+
fixture.componentInstance.scrollStrategy = overlay.scrollStrategies.close();
562+
fixture.componentInstance.isOpen = true;
563+
fixture.detectChanges();
564+
565+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
566+
567+
scrolledSubject.next();
568+
fixture.detectChanges();
569+
570+
expect(fixture.componentInstance.detachHandler).toHaveBeenCalled();
571+
}));
572+
537573
});
538574

539575
});
540576

541-
542577
@Component({
543578
template: `
544579
<button cdk-overlay-origin id="trigger" #trigger="cdkOverlayOrigin">Toggle menu</button>
@@ -554,6 +589,7 @@ describe('Overlay directives', () => {
554589
[cdkConnectedOverlayFlexibleDimensions]="flexibleDimensions"
555590
[cdkConnectedOverlayGrowAfterOpen]="growAfterOpen"
556591
[cdkConnectedOverlayPush]="push"
592+
[cdkConnectedOverlayScrollStrategy]="scrollStrategy"
557593
cdkConnectedOverlayBackdropClass="mat-test-class"
558594
cdkConnectedOverlayPanelClass="cdk-test-panel-class"
559595
(backdropClick)="backdropClickHandler($event)"
@@ -587,13 +623,14 @@ class ConnectedOverlayDirectiveTest {
587623
flexibleDimensions: boolean;
588624
growAfterOpen: boolean;
589625
push: boolean;
626+
scrollStrategy: ScrollStrategy;
590627
backdropClickHandler = jasmine.createSpy('backdropClick handler');
591628
positionChangeHandler = jasmine.createSpy('positionChange handler');
592629
keydownHandler = jasmine.createSpy('keydown handler');
593630
positionOverrides: ConnectionPositionPair[];
594631
attachHandler = jasmine.createSpy('attachHandler').and.callFake(() => {
595-
this.attachResult =
596-
this.connectedOverlayDirective.overlayRef.overlayElement.querySelector('p') as HTMLElement;
632+
const overlayElement = this.connectedOverlayDirective.overlayRef.overlayElement;
633+
this.attachResult = overlayElement.querySelector('p') as HTMLElement;
597634
});
598635
detachHandler = jasmine.createSpy('detachHandler');
599636
attachResult: HTMLElement;

src/cdk/overlay/overlay-directives.ts

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

273273
this._overlayRef = this._overlay.create(this._buildConfig());
274-
274+
this._overlayRef.attachments().subscribe(() => this.attach.emit());
275+
this._overlayRef.detachments().subscribe(() => this.detach.emit());
275276
this._overlayRef.keydownEvents().subscribe((event: KeyboardEvent) => {
276277
this.overlayKeydown.next(event);
277278

@@ -362,7 +363,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
362363

363364
if (!this._overlayRef.hasAttached()) {
364365
this._overlayRef.attach(this._templatePortal);
365-
this.attach.emit();
366366
}
367367

368368
if (this.hasBackdrop) {
@@ -378,7 +378,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
378378
private _detachOverlay() {
379379
if (this._overlayRef) {
380380
this._overlayRef.detach();
381-
this.detach.emit();
382381
}
383382

384383
this._backdropSubscription.unsubscribe();

0 commit comments

Comments
 (0)