Skip to content

Commit 554fe0f

Browse files
committed
wip: shared styles host
1 parent c4b63b4 commit 554fe0f

File tree

1 file changed

+34
-2
lines changed

1 file changed

+34
-2
lines changed

packages/platform-browser/src/dom/shared_styles_host.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ export class SharedStylesHost implements ɵSharedStylesHost, OnDestroy {
121121
*/
122122
private readonly hosts = new Map<Node, number>();
123123

124+
/** Whether this instance has been destroyed. */
125+
private destroyed = false;
126+
124127
constructor(
125128
@Inject(DOCUMENT) private readonly doc: Document,
126129
@Inject(APP_ID) private readonly appId: string,
@@ -202,24 +205,53 @@ export class SharedStylesHost implements ɵSharedStylesHost, OnDestroy {
202205
removeElements(elements);
203206
}
204207
this.hosts.clear();
208+
this.destroyed = true;
205209
}
206210

207211
addHost(hostNode: Node): void {
212+
// Adding a host after destruction will have no effect and is likely a bug in the caller.
213+
// However, some testing scenarios with fake async appear to trigger CD after `TestBed`
214+
// teardown, meaning Angular may render new components (and then immediately destroy them)
215+
// after the application is destroyed, so we have to allow this to happen and no-op.
216+
if (this.destroyed) return;
217+
208218
const existingUsage = this.hosts.get(hostNode) ?? 0;
209219
if (existingUsage === 0) {
220+
// // Add existing styles to new host
221+
// for (const [style, {elements}] of this.inline) {
222+
// elements.push(this.addElement(hostNode, createStyleElement(style, this.doc)));
223+
// }
224+
// for (const [url, {elements}] of this.external) {
225+
// elements.push(this.addElement(hostNode, createLinkElement(url, this.doc)));
226+
// }
227+
210228
// Add existing styles to new host
211229
for (const [style, {elements}] of this.inline) {
212-
elements.push(this.addElement(hostNode, createStyleElement(style, this.doc)));
230+
// `removeHost` currently does not actually remove styles when usage drops to zero.
231+
// Therefore removing a host to zero and then re-adding to one, could cause Angular
232+
// to duplicate the styles on the page. This check makes sure we don't add the styles
233+
// more than once.
234+
if (!elements.some((e) => e.parentNode === hostNode)) {
235+
elements.push(this.addElement(hostNode, createStyleElement(style, this.doc)));
236+
}
213237
}
214238
for (const [url, {elements}] of this.external) {
215-
elements.push(this.addElement(hostNode, createLinkElement(url, this.doc)));
239+
if (!elements.some((e) => e.parentNode === hostNode)) {
240+
elements.push(this.addElement(hostNode, createLinkElement(url, this.doc)));
241+
}
216242
}
217243
}
218244

219245
this.hosts.set(hostNode, existingUsage + 1);
220246
}
221247

222248
removeHost(hostNode: Node): void {
249+
// In some scenarios (such as an explicit `ApplicationRef.prototype.destroy` call),
250+
// this instance's `ngOnDestroy` method may be called before a component is destroyed and
251+
// attempts to remove its own styles. In this case, we need to no-op to avoid throwing an
252+
// error.
253+
if (this.destroyed) return;
254+
223255
const usage = this.hosts.get(hostNode);
224256
if (typeof ngDevMode !== 'undefined' && ngDevMode && usage === undefined) {
225257
throw new Error('Attempted to remove a host which was not added.');

0 commit comments

Comments
 (0)