Skip to content

Commit d45b7a9

Browse files
dgp1130atscott
authored andcommitted
fix(platform-browser): remove unused styles when associated host is dropped
This fixes a memory leak in `SharedStylesHost` where calling `removeHost` would leave any `<style>` or `<link>` tags associated with that host in the DOM. This is wasteful in general, and can create even more leaks if the same host is added and removed multiple times, causing styles to be consistently reappended but never removed. BREAKING CHANGE: This removes styles when they appear to no longer be used by an associated `host`. However other DOM on the page may still be affected by those styles if not leveraging `ViewEncapsulation.Emulated` or if those styles are used by elements outside of Angular, potentially causing other DOM to appear unstyled.
1 parent 6e1e976 commit d45b7a9

File tree

2 files changed

+39
-0
lines changed

2 files changed

+39
-0
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,18 @@ export class SharedStylesHost implements OnDestroy {
226226

227227
removeHost(hostNode: Node): void {
228228
this.hosts.delete(hostNode);
229+
230+
for (const record of [...this.inline.values(), ...this.external.values()]) {
231+
const remaining: Array<HTMLStyleElement | HTMLLinkElement> = [];
232+
for (const element of record.elements) {
233+
if (element.parentNode === hostNode) {
234+
element.remove();
235+
} else {
236+
remaining.push(element);
237+
}
238+
}
239+
record.elements = remaining;
240+
}
229241
}
230242

231243
private addElement<T extends HTMLElement>(host: Node, element: T): T {

packages/platform-browser/test/dom/shared_styles_host_spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,31 @@ describe('SharedStylesHost', () => {
142142
expect(doc.head.innerHTML).not.toContain('ng-app-id');
143143
});
144144
});
145+
146+
describe('removeHost', () => {
147+
it('should remove inline style nodes from the host', () => {
148+
ssh.addStyles(['a {}']);
149+
ssh.addHost(someHost);
150+
expect(someHost.innerHTML).toEqual('<style>a {}</style>');
151+
152+
ssh.removeHost(someHost);
153+
expect(someHost.innerHTML).toEqual('');
154+
});
155+
156+
it('should remove external style nodes from the host', () => {
157+
ssh.addStyles([], ['component.css']);
158+
ssh.addHost(someHost);
159+
expect(someHost.innerHTML).toEqual('<link rel="stylesheet" href="component.css">');
160+
161+
ssh.removeHost(someHost);
162+
expect(someHost.innerHTML).toEqual('');
163+
});
164+
165+
it('should not add new styles to the host after removal', () => {
166+
ssh.addHost(someHost);
167+
ssh.removeHost(someHost);
168+
ssh.addStyles(['a {}']);
169+
expect(someHost.innerHTML).toEqual('');
170+
});
171+
});
145172
});

0 commit comments

Comments
 (0)