Skip to content

Commit 3f025c6

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 093d007 commit 3f025c6

File tree

2 files changed

+48
-12
lines changed

2 files changed

+48
-12
lines changed

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

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,21 @@ import {
88
dispatchEvent,
99
} from '@angular/cdk/testing/private';
1010
import {ESCAPE, A} from '@angular/cdk/keycodes';
11-
import {Overlay, CdkConnectedOverlay, OverlayModule, CdkOverlayOrigin} from './index';
11+
import {
12+
Overlay,
13+
CdkConnectedOverlay,
14+
OverlayModule,
15+
CdkOverlayOrigin,
16+
ScrollDispatcher,
17+
ScrollStrategy,
18+
} from './index';
1219
import {OverlayContainer} from './overlay-container';
1320
import {
1421
ConnectedOverlayPositionChange,
1522
ConnectionPositionPair,
1623
} from './position/connected-position';
1724
import {FlexibleConnectedPositionStrategy} from './position/flexible-connected-position-strategy';
25+
import {Subject} from 'rxjs';
1826

1927

2028
describe('Overlay directives', () => {
@@ -23,12 +31,17 @@ describe('Overlay directives', () => {
2331
let overlayContainerElement: HTMLElement;
2432
let fixture: ComponentFixture<ConnectedOverlayDirectiveTest>;
2533
let dir: {value: string};
34+
let scrolledSubject = new Subject();
2635

2736
beforeEach(() => {
2837
TestBed.configureTestingModule({
2938
imports: [OverlayModule],
3039
declarations: [ConnectedOverlayDirectiveTest, ConnectedOverlayPropertyInitOrder],
31-
providers: [{provide: Directionality, useFactory: () => dir = {value: 'ltr'}}],
40+
providers: [{provide: Directionality, useFactory: () => dir = {value: 'ltr'}},
41+
{provide: ScrollDispatcher, useFactory: () => ({
42+
scrolled: () => scrolledSubject.asObservable()
43+
})}
44+
],
3245
});
3346
});
3447

@@ -529,7 +542,7 @@ describe('Overlay directives', () => {
529542
});
530543

531544
describe('outputs', () => {
532-
it('should emit backdropClick appropriately', () => {
545+
it('should emit when the backdrop was clicked', () => {
533546
fixture.componentInstance.hasBackdrop = true;
534547
fixture.componentInstance.isOpen = true;
535548
fixture.detectChanges();
@@ -543,7 +556,7 @@ describe('Overlay directives', () => {
543556
.toHaveBeenCalledWith(jasmine.any(MouseEvent));
544557
});
545558

546-
it('should emit positionChange appropriately', () => {
559+
it('should emit when the position has changed', () => {
547560
expect(fixture.componentInstance.positionChangeHandler).not.toHaveBeenCalled();
548561
fixture.componentInstance.isOpen = true;
549562
fixture.detectChanges();
@@ -556,15 +569,24 @@ describe('Overlay directives', () => {
556569
.toBe(true, `Expected directive to emit an instance of ConnectedOverlayPositionChange.`);
557570
});
558571

559-
it('should emit attach and detach appropriately', () => {
572+
it('should emit when attached', () => {
560573
expect(fixture.componentInstance.attachHandler).not.toHaveBeenCalled();
561-
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
562574
fixture.componentInstance.isOpen = true;
563575
fixture.detectChanges();
564576

565577
expect(fixture.componentInstance.attachHandler).toHaveBeenCalled();
566578
expect(fixture.componentInstance.attachResult instanceof HTMLElement)
567579
.toBe(true, `Expected pane to be populated with HTML elements when attach was called.`);
580+
581+
fixture.componentInstance.isOpen = false;
582+
fixture.detectChanges();
583+
});
584+
585+
it('should emit when detached', () => {
586+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
587+
fixture.componentInstance.isOpen = true;
588+
fixture.detectChanges();
589+
568590
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
569591

570592
fixture.componentInstance.isOpen = false;
@@ -584,11 +606,24 @@ describe('Overlay directives', () => {
584606
expect(fixture.componentInstance.keydownHandler).toHaveBeenCalledWith(event);
585607
});
586608

609+
it('should emit when detached externally', () => {
610+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
611+
fixture.componentInstance.scrollStrategy = overlay.scrollStrategies.close();
612+
fixture.componentInstance.isOpen = true;
613+
fixture.detectChanges();
614+
615+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
616+
617+
scrolledSubject.next();
618+
fixture.detectChanges();
619+
620+
expect(fixture.componentInstance.detachHandler).toHaveBeenCalled();
621+
});
622+
587623
});
588624

589625
});
590626

591-
592627
@Component({
593628
template: `
594629
<button cdk-overlay-origin id="trigger" #trigger="cdkOverlayOrigin">Toggle menu</button>
@@ -605,6 +640,7 @@ describe('Overlay directives', () => {
605640
[cdkConnectedOverlayFlexibleDimensions]="flexibleDimensions"
606641
[cdkConnectedOverlayGrowAfterOpen]="growAfterOpen"
607642
[cdkConnectedOverlayPush]="push"
643+
[cdkConnectedOverlayScrollStrategy]="scrollStrategy"
608644
cdkConnectedOverlayBackdropClass="mat-test-class"
609645
cdkConnectedOverlayPanelClass="cdk-test-panel-class"
610646
(backdropClick)="backdropClickHandler($event)"
@@ -640,13 +676,14 @@ class ConnectedOverlayDirectiveTest {
640676
flexibleDimensions: boolean;
641677
growAfterOpen: boolean;
642678
push: boolean;
679+
scrollStrategy: ScrollStrategy;
643680
backdropClickHandler = jasmine.createSpy('backdropClick handler');
644681
positionChangeHandler = jasmine.createSpy('positionChange handler');
645682
keydownHandler = jasmine.createSpy('keydown handler');
646683
positionOverrides: ConnectionPositionPair[];
647684
attachHandler = jasmine.createSpy('attachHandler').and.callFake(() => {
648-
this.attachResult =
649-
this.connectedOverlayDirective.overlayRef.overlayElement.querySelector('p') as HTMLElement;
685+
const overlayElement = this.connectedOverlayDirective.overlayRef.overlayElement;
686+
this.attachResult = overlayElement.querySelector('p') as HTMLElement;
650687
});
651688
detachHandler = jasmine.createSpy('detachHandler');
652689
attachResult: HTMLElement;

src/cdk/overlay/overlay-directives.ts

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

282282
this._overlayRef = this._overlay.create(this._buildConfig());
283-
283+
this._overlayRef.attachments().subscribe(() => this.attach.emit());
284+
this._overlayRef.detachments().subscribe(() => this.detach.emit());
284285
this._overlayRef.keydownEvents().subscribe((event: KeyboardEvent) => {
285286
this.overlayKeydown.next(event);
286287

@@ -373,7 +374,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
373374

374375
if (!this._overlayRef.hasAttached()) {
375376
this._overlayRef.attach(this._templatePortal);
376-
this.attach.emit();
377377
}
378378

379379
if (this.hasBackdrop) {
@@ -389,7 +389,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
389389
private _detachOverlay() {
390390
if (this._overlayRef) {
391391
this._overlayRef.detach();
392-
this.detach.emit();
393392
}
394393

395394
this._backdropSubscription.unsubscribe();

0 commit comments

Comments
 (0)