Skip to content

Commit 4dac772

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 3352201 commit 4dac772

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

@@ -310,7 +323,7 @@ describe('Overlay directives', () => {
310323
});
311324

312325
describe('outputs', () => {
313-
it('should emit backdropClick appropriately', () => {
326+
it('should emit when the backdrop was clicked', () => {
314327
fixture.componentInstance.hasBackdrop = true;
315328
fixture.componentInstance.isOpen = true;
316329
fixture.detectChanges();
@@ -323,7 +336,7 @@ describe('Overlay directives', () => {
323336
expect(fixture.componentInstance.backdropClicked).toBe(true);
324337
});
325338

326-
it('should emit positionChange appropriately', () => {
339+
it('should emit when the position has changed', () => {
327340
expect(fixture.componentInstance.positionChangeHandler).not.toHaveBeenCalled();
328341
fixture.componentInstance.isOpen = true;
329342
fixture.detectChanges();
@@ -336,34 +349,57 @@ describe('Overlay directives', () => {
336349
.toBe(true, `Expected directive to emit an instance of ConnectedOverlayPositionChange.`);
337350
});
338351

339-
it('should emit attach and detach appropriately', () => {
352+
it('should emit when attached', () => {
340353
expect(fixture.componentInstance.attachHandler).not.toHaveBeenCalled();
341-
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
342354
fixture.componentInstance.isOpen = true;
343355
fixture.detectChanges();
344356

345357
expect(fixture.componentInstance.attachHandler).toHaveBeenCalled();
346358
expect(fixture.componentInstance.attachResult instanceof HTMLElement)
347359
.toBe(true, `Expected pane to be populated with HTML elements when attach was called.`);
360+
361+
fixture.componentInstance.isOpen = false;
362+
fixture.detectChanges();
363+
});
364+
365+
it('should emit when detached', () => {
366+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
367+
fixture.componentInstance.isOpen = true;
368+
fixture.detectChanges();
369+
348370
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
349371

350372
fixture.componentInstance.isOpen = false;
351373
fixture.detectChanges();
352374
expect(fixture.componentInstance.detachHandler).toHaveBeenCalled();
353375
});
354376

377+
it('should emit when detached externally', inject([Overlay], (overlay: Overlay) => {
378+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
379+
fixture.componentInstance.scrollStrategy = overlay.scrollStrategies.close();
380+
fixture.componentInstance.isOpen = true;
381+
fixture.detectChanges();
382+
383+
expect(fixture.componentInstance.detachHandler).not.toHaveBeenCalled();
384+
385+
scrolledSubject.next();
386+
fixture.detectChanges();
387+
388+
expect(fixture.componentInstance.detachHandler).toHaveBeenCalled();
389+
}));
390+
355391
});
356392

357393
});
358394

359-
360395
@Component({
361396
template: `
362397
<button cdk-overlay-origin id="trigger" #trigger="cdkOverlayOrigin">Toggle menu</button>
363398
<button cdk-overlay-origin id="otherTrigger" #otherTrigger="cdkOverlayOrigin">Toggle menu</button>
364399
365400
<ng-template cdk-connected-overlay [open]="isOpen" [width]="width" [height]="height"
366401
[cdkConnectedOverlayOrigin]="triggerOverride || trigger"
402+
[scrollStrategy]="scrollStrategy"
367403
[hasBackdrop]="hasBackdrop" backdropClass="mat-test-class"
368404
(backdropClick)="backdropClicked=true" [offsetX]="offsetX" [offsetY]="offsetY"
369405
(positionChange)="positionChangeHandler($event)" (attach)="attachHandler()"
@@ -387,11 +423,12 @@ class ConnectedOverlayDirectiveTest {
387423
triggerOverride: CdkOverlayOrigin;
388424
hasBackdrop: boolean;
389425
backdropClicked = false;
426+
scrollStrategy: ScrollStrategy;
390427
positionChangeHandler = jasmine.createSpy('positionChangeHandler');
391428
positionOverrides: ConnectionPositionPair[];
392429
attachHandler = jasmine.createSpy('attachHandler').and.callFake(() => {
393-
this.attachResult =
394-
this.connectedOverlayDirective.overlayRef.overlayElement.querySelector('p') as HTMLElement;
430+
const overlayElement = this.connectedOverlayDirective.overlayRef.overlayElement;
431+
this.attachResult = overlayElement.querySelector('p') as HTMLElement;
395432
});
396433
detachHandler = jasmine.createSpy('detachHandler');
397434
attachResult: HTMLElement;

src/cdk/overlay/overlay-directives.ts

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

320320
this._overlayRef = this._overlay.create(this._buildConfig());
321+
this._overlayRef.attachments().subscribe(() => this.attach.emit());
322+
this._overlayRef.detachments().subscribe(() => this.detach.emit());
321323
}
322324

323325
/** Builds the overlay config based on the directive's inputs */
@@ -392,7 +394,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
392394

393395
if (!this._overlayRef.hasAttached()) {
394396
this._overlayRef.attach(this._templatePortal);
395-
this.attach.emit();
396397
}
397398

398399
if (this.hasBackdrop) {
@@ -406,7 +407,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges {
406407
private _detachOverlay() {
407408
if (this._overlayRef) {
408409
this._overlayRef.detach();
409-
this.detach.emit();
410410
}
411411

412412
this._backdropSubscription.unsubscribe();

0 commit comments

Comments
 (0)