Skip to content

Commit 154bb55

Browse files
jelbournkara
authored andcommitted
fix(overlay): remove overlay container on destroy (#5378)
1 parent 363562f commit 154bb55

File tree

2 files changed

+71
-2
lines changed

2 files changed

+71
-2
lines changed

src/cdk/overlay/overlay-container.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Injectable, Optional, SkipSelf} from '@angular/core';
9+
import {Injectable, Optional, SkipSelf, OnDestroy} from '@angular/core';
1010

1111

1212
/**
1313
* The OverlayContainer is the container in which all overlays will load.
1414
* It should be provided in the root component to ensure it is properly shared.
1515
*/
1616
@Injectable()
17-
export class OverlayContainer {
17+
export class OverlayContainer implements OnDestroy {
1818
protected _containerElement: HTMLElement;
1919

2020
private _themeClass: string;
@@ -37,6 +37,12 @@ export class OverlayContainer {
3737
this._themeClass = value;
3838
}
3939

40+
ngOnDestroy() {
41+
if (this._containerElement && this._containerElement.parentNode) {
42+
this._containerElement.parentNode.removeChild(this._containerElement);
43+
}
44+
}
45+
4046
/**
4147
* This method returns the overlay container element. It will lazily
4248
* create the element the first time it is called to facilitate using
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import {async, inject, TestBed} from '@angular/core/testing';
2+
import {Component, NgModule, ViewChild, ViewContainerRef} from '@angular/core';
3+
import {PortalModule, TemplatePortalDirective} from '../portal/portal-directives';
4+
import {Overlay, OverlayContainer, OverlayModule} from './index';
5+
6+
describe('OverlayContainer', () => {
7+
let overlay: Overlay;
8+
let overlayContainer: OverlayContainer;
9+
10+
beforeAll(() => {
11+
// Remove any stale overlay containers from previous tests that didn't clean up correctly.
12+
const staleContainers = document.querySelectorAll('.cdk-overlay-container');
13+
for (let i = staleContainers.length - 1; i >= 0; i--) {
14+
staleContainers[i].parentNode!.removeChild(staleContainers[i]);
15+
}
16+
});
17+
18+
beforeEach(async(() => {
19+
TestBed.configureTestingModule({
20+
imports: [OverlayTestModule]
21+
}).compileComponents();
22+
}));
23+
24+
beforeEach(inject([Overlay, OverlayContainer], (o: Overlay, oc: OverlayContainer) => {
25+
overlay = o;
26+
overlayContainer = oc;
27+
}));
28+
29+
it('should remove the overlay container element from the DOM on destruction', () => {
30+
const fixture = TestBed.createComponent(TestComponentWithTemplatePortals);
31+
32+
const overlayRef = overlay.create();
33+
overlayRef.attach(fixture.componentInstance.templatePortal);
34+
fixture.detectChanges();
35+
36+
expect(document.querySelectorAll('.cdk-overlay-container'))
37+
.not.toBeNull('Expected the overlay container to be in the DOM after opening an overlay');
38+
39+
// Manually call `ngOnDestroy` because there is no way to force Angular to destroy an
40+
// injectable in a unit test.
41+
overlayContainer.ngOnDestroy();
42+
43+
expect(document.querySelector('.cdk-overlay-container'))
44+
.toBeNull('Expected the overlay container *not* to be in the DOM after destruction');
45+
});
46+
});
47+
48+
/** Test-bed component that contains a TempatePortal and an ElementRef. */
49+
@Component({
50+
template: `<ng-template cdk-portal>Cake</ng-template>`,
51+
providers: [Overlay],
52+
})
53+
class TestComponentWithTemplatePortals {
54+
@ViewChild(TemplatePortalDirective) templatePortal: TemplatePortalDirective;
55+
56+
constructor(public viewContainerRef: ViewContainerRef) { }
57+
}
58+
59+
@NgModule({
60+
imports: [OverlayModule, PortalModule],
61+
declarations: [TestComponentWithTemplatePortals]
62+
})
63+
class OverlayTestModule { }

0 commit comments

Comments
 (0)