Skip to content

Commit bcda183

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 3255cf3 commit bcda183

File tree

2 files changed

+49
-12
lines changed

2 files changed

+49
-12
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

@@ -404,7 +417,7 @@ describe('Overlay directives', () => {
404417
});
405418

406419
describe('outputs', () => {
407-
it('should emit backdropClick appropriately', () => {
420+
it('should emit when the backdrop was clicked', () => {
408421
fixture.componentInstance.hasBackdrop = true;
409422
fixture.componentInstance.isOpen = true;
410423
fixture.detectChanges();
@@ -418,7 +431,7 @@ describe('Overlay directives', () => {
418431
.toHaveBeenCalledWith(jasmine.any(MouseEvent));
419432
});
420433

421-
it('should emit positionChange appropriately', () => {
434+
it('should emit when the position has changed', () => {
422435
expect(fixture.componentInstance.positionChangeHandler).not.toHaveBeenCalled();
423436
fixture.componentInstance.isOpen = true;
424437
fixture.detectChanges();
@@ -431,15 +444,24 @@ describe('Overlay directives', () => {
431444
.toBe(true, `Expected directive to emit an instance of ConnectedOverlayPositionChange.`);
432445
});
433446

434-
it('should emit attach and detach appropriately', () => {
447+
it('should emit when attached', () => {
435448
expect(fixture.componentInstance.attachHandler).not.toHaveBeenCalled();
436-
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
437449
fixture.componentInstance.isOpen = true;
438450
fixture.detectChanges();
439451

440452
expect(fixture.componentInstance.attachHandler).toHaveBeenCalled();
441453
expect(fixture.componentInstance.attachResult instanceof HTMLElement)
442454
.toBe(true, `Expected pane to be populated with HTML elements when attach was called.`);
455+
456+
fixture.componentInstance.isOpen = false;
457+
fixture.detectChanges();
458+
});
459+
460+
it('should emit when detached', () => {
461+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
462+
fixture.componentInstance.isOpen = true;
463+
fixture.detectChanges();
464+
443465
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
444466

445467
fixture.componentInstance.isOpen = false;
@@ -459,11 +481,24 @@ describe('Overlay directives', () => {
459481
expect(fixture.componentInstance.keydownHandler).toHaveBeenCalledWith(event);
460482
});
461483

484+
it('should emit when detached externally', inject([Overlay], (overlay: Overlay) => {
485+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
486+
fixture.componentInstance.scrollStrategy = overlay.scrollStrategies.close();
487+
fixture.componentInstance.isOpen = true;
488+
fixture.detectChanges();
489+
490+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
491+
492+
scrolledSubject.next();
493+
fixture.detectChanges();
494+
495+
expect(fixture.componentInstance.detachHandler).toHaveBeenCalled();
496+
}));
497+
462498
});
463499

464500
});
465501

466-
467502
@Component({
468503
template: `
469504
<button cdk-overlay-origin id="trigger" #trigger="cdkOverlayOrigin">Toggle menu</button>
@@ -479,6 +514,7 @@ describe('Overlay directives', () => {
479514
[cdkConnectedOverlayFlexibleDimensions]="flexibleDimensions"
480515
[cdkConnectedOverlayGrowAfterOpen]="growAfterOpen"
481516
[cdkConnectedOverlayPush]="push"
517+
[cdkConnectedOverlayScrollStrategy]="scrollStrategy"
482518
cdkConnectedOverlayBackdropClass="mat-test-class"
483519
(backdropClick)="backdropClickHandler($event)"
484520
[cdkConnectedOverlayOffsetX]="offsetX"
@@ -511,13 +547,14 @@ class ConnectedOverlayDirectiveTest {
511547
flexibleDimensions: boolean;
512548
growAfterOpen: boolean;
513549
push: boolean;
550+
scrollStrategy: ScrollStrategy;
514551
backdropClickHandler = jasmine.createSpy('backdropClick handler');
515552
positionChangeHandler = jasmine.createSpy('positionChange handler');
516553
keydownHandler = jasmine.createSpy('keydown handler');
517554
positionOverrides: ConnectionPositionPair[];
518555
attachHandler = jasmine.createSpy('attachHandler').and.callFake(() => {
519-
this.attachResult =
520-
this.connectedOverlayDirective.overlayRef.overlayElement.querySelector('p') as HTMLElement;
556+
const overlayElement = this.connectedOverlayDirective.overlayRef.overlayElement;
557+
this.attachResult = overlayElement.querySelector('p') as HTMLElement;
521558
});
522559
detachHandler = jasmine.createSpy('detachHandler');
523560
attachResult: HTMLElement;

src/cdk/overlay/overlay-directives.ts

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

266266
this._overlayRef = this._overlay.create(this._buildConfig());
267+
this._overlayRef.attachments().subscribe(() => this.attach.emit());
268+
this._overlayRef.detachments().subscribe(() => this.detach.emit());
267269
}
268270

269271
/** Builds the overlay config based on the directive's inputs */
@@ -356,7 +358,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
356358

357359
if (!this._overlayRef.hasAttached()) {
358360
this._overlayRef.attach(this._templatePortal);
359-
this.attach.emit();
360361
}
361362

362363
if (this.hasBackdrop) {
@@ -370,7 +371,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
370371
private _detachOverlay() {
371372
if (this._overlayRef) {
372373
this._overlayRef.detach();
373-
this.detach.emit();
374374
}
375375

376376
this._backdropSubscription.unsubscribe();

0 commit comments

Comments
 (0)