Skip to content

Commit 97c67f1

Browse files
committed
fix(overlay): only clear duplicate containers from different platform
Currently we clear all overlay containers when we create a new one as a way to avoid duplicate content coming in from the server. Our current approach is a little too aggressive, because it can pick up containers from different apps. These changes add an extra attribute to the container so that we can determine which platform it's coming from. Fixes #16851.
1 parent a30094b commit 97c67f1

File tree

5 files changed

+57
-16
lines changed

5 files changed

+57
-16
lines changed

src/cdk/overlay/fullscreen-overlay-container.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import {Injectable, Inject, OnDestroy} from '@angular/core';
1010
import {OverlayContainer} from './overlay-container';
1111
import {DOCUMENT} from '@angular/common';
12+
import {Platform} from '@angular/cdk/platform';
1213

1314

1415
/**
@@ -23,8 +24,14 @@ export class FullscreenOverlayContainer extends OverlayContainer implements OnDe
2324
private _fullScreenEventName: string | undefined;
2425
private _fullScreenListener: () => void;
2526

26-
constructor(@Inject(DOCUMENT) _document: any) {
27-
super(_document);
27+
constructor(
28+
@Inject(DOCUMENT) _document: any,
29+
/**
30+
* @deprecated `platform` parameter to become required.
31+
* @breaking-change 10.0.0
32+
*/
33+
platform?: Platform) {
34+
super(_document, platform);
2835
}
2936

3037
ngOnDestroy() {

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,10 @@ describe('OverlayContainer', () => {
5353
.toBe(false, 'Expected the overlay container not to have class "commander-shepard"');
5454
});
5555

56-
it('should ensure that there is only one overlay container on the page', () => {
56+
it('should remove overlay containers from the server when on the browser', () => {
5757
const extraContainer = document.createElement('div');
5858
extraContainer.classList.add('cdk-overlay-container');
59+
extraContainer.setAttribute('platform', 'server');
5960
document.body.appendChild(extraContainer);
6061

6162
overlayContainer.getContainerElement();
@@ -65,6 +66,19 @@ describe('OverlayContainer', () => {
6566
extraContainer.parentNode.removeChild(extraContainer);
6667
}
6768
});
69+
70+
it('should not remove extra containers that were created on the browser', () => {
71+
const extraContainer = document.createElement('div');
72+
extraContainer.classList.add('cdk-overlay-container');
73+
document.body.appendChild(extraContainer);
74+
75+
overlayContainer.getContainerElement();
76+
77+
expect(document.querySelectorAll('.cdk-overlay-container').length).toBe(2);
78+
79+
extraContainer.parentNode!.removeChild(extraContainer);
80+
});
81+
6882
});
6983

7084
/** Test-bed component that contains a TempatePortal and an ElementRef. */

src/cdk/overlay/overlay-container.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
Optional,
1616
SkipSelf,
1717
} from '@angular/core';
18+
import {Platform} from '@angular/cdk/platform';
1819

1920

2021
/** Container inside which all overlays will render. */
@@ -23,13 +24,21 @@ export class OverlayContainer implements OnDestroy {
2324
protected _containerElement: HTMLElement;
2425
protected _document: Document;
2526

26-
constructor(@Inject(DOCUMENT) document: any) {
27+
constructor(
28+
@Inject(DOCUMENT) document: any,
29+
/**
30+
* @deprecated `platform` parameter to become required.
31+
* @breaking-change 10.0.0
32+
*/
33+
protected _platform?: Platform) {
2734
this._document = document;
2835
}
2936

3037
ngOnDestroy() {
31-
if (this._containerElement && this._containerElement.parentNode) {
32-
this._containerElement.parentNode.removeChild(this._containerElement);
38+
const container = this._containerElement;
39+
40+
if (container && container.parentNode) {
41+
container.parentNode.removeChild(container);
3342
}
3443
}
3544

@@ -52,16 +61,28 @@ export class OverlayContainer implements OnDestroy {
5261
* with the 'cdk-overlay-container' class on the document body.
5362
*/
5463
protected _createContainer(): void {
64+
// @breaking-change 10.0.0 Remove null check for `_platform`.
65+
const isBrowser = this._platform ? this._platform.isBrowser : typeof window !== 'undefined';
5566
const containerClass = 'cdk-overlay-container';
56-
const previousContainers = this._document.getElementsByClassName(containerClass);
5767

58-
// Remove any old containers. This can happen when transitioning from the server to the client.
59-
for (let i = 0; i < previousContainers.length; i++) {
60-
previousContainers[i].parentNode!.removeChild(previousContainers[i]);
68+
if (isBrowser) {
69+
const oppositePlatformContainers =
70+
this._document.querySelectorAll(`.${containerClass}[platform="server"]`);
71+
72+
// Remove any old containers from the opposite platform.
73+
// This can happen when transitioning from the server to the client.
74+
for (let i = 0; i < oppositePlatformContainers.length; i++) {
75+
oppositePlatformContainers[i].parentNode!.removeChild(oppositePlatformContainers[i]);
76+
}
6177
}
6278

6379
const container = this._document.createElement('div');
6480
container.classList.add(containerClass);
81+
82+
if (!isBrowser) {
83+
container.setAttribute('platform', 'server');
84+
}
85+
6586
this._document.body.appendChild(container);
6687
this._containerElement = container;
6788
}

src/material/select/testing/shared.spec.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,13 +226,11 @@ function getActiveElementId() {
226226
<mat-option *ngFor="let state of states" [value]="state.code">{{ state.name }}</mat-option>
227227
</mat-select>
228228
</mat-form-field>
229-
230229
<mat-form-field>
231230
<mat-select multiple id="multiple-selection">
232231
<mat-option *ngFor="let state of states" [value]="state.code">{{ state.name }}</mat-option>
233232
</mat-select>
234233
</mat-form-field>
235-
236234
<mat-form-field>
237235
<mat-select id="grouped">
238236
<mat-optgroup *ngFor="let group of stateGroups" [label]="group.name">
@@ -242,7 +240,6 @@ function getActiveElementId() {
242240
</mat-optgroup>
243241
</mat-select>
244242
</mat-form-field>
245-
246243
<mat-form-field>
247244
<mat-select [formControl]="formControl" id="with-form-control">
248245
<mat-option *ngFor="let state of states" [value]="state.code">{{ state.name }}</mat-option>
@@ -283,4 +280,3 @@ class SelectHarnessTest {
283280
}
284281
];
285282
}
286-

tools/public_api_guard/cdk/overlay.d.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ export declare type FlexibleConnectedPositionStrategyOrigin = ElementRef | HTMLE
145145
};
146146

147147
export declare class FullscreenOverlayContainer extends OverlayContainer implements OnDestroy {
148-
constructor(_document: any);
148+
constructor(_document: any,
149+
platform?: Platform);
149150
protected _createContainer(): void;
150151
getFullscreenElement(): Element;
151152
ngOnDestroy(): void;
@@ -217,7 +218,9 @@ export interface OverlayConnectionPosition {
217218
export declare class OverlayContainer implements OnDestroy {
218219
protected _containerElement: HTMLElement;
219220
protected _document: Document;
220-
constructor(document: any);
221+
protected _platform?: Platform | undefined;
222+
constructor(document: any,
223+
_platform?: Platform | undefined);
221224
protected _createContainer(): void;
222225
getContainerElement(): HTMLElement;
223226
ngOnDestroy(): void;

0 commit comments

Comments
 (0)