From e34c33cd46535b62b1578d4c632da90d8f2d6654 Mon Sep 17 00:00:00 2001 From: Joey Perrott Date: Thu, 6 Aug 2020 10:59:20 -0700 Subject: [PATCH] fix(platform-server): remove styles added by ServerStylesHost on destruction (#38367) When a ServerStylesHost instance is destroyed, all of the shared styles added to the DOM head element by that instance should be removed. Without this removal, over time a large number of style rules will build up and cause extra memory pressure. This brings the ServerStylesHost in line with the DomStylesHost used by the platform browser, which performs this same cleanup. PR Close #38367 --- packages/platform-server/src/styles_host.ts | 6 +++ .../test/server_styles_host_spec.ts | 50 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 packages/platform-server/test/server_styles_host_spec.ts diff --git a/packages/platform-server/src/styles_host.ts b/packages/platform-server/src/styles_host.ts index 47aeba45d2..e8364cb183 100644 --- a/packages/platform-server/src/styles_host.ts +++ b/packages/platform-server/src/styles_host.ts @@ -13,6 +13,7 @@ import {ɵSharedStylesHost as SharedStylesHost, ɵTRANSITION_ID} from '@angular/ @Injectable() export class ServerStylesHost extends SharedStylesHost { private head: any = null; + private _styleNodes = new Set(); constructor( @Inject(DOCUMENT) private doc: any, @@ -29,9 +30,14 @@ export class ServerStylesHost extends SharedStylesHost { el.setAttribute('ng-transition', this.transitionId); } this.head.appendChild(el); + this._styleNodes.add(el); } onStylesAdded(additions: Set) { additions.forEach(style => this._addStyle(style)); } + + ngOnDestroy() { + this._styleNodes.forEach(styleNode => styleNode.remove()); + } } diff --git a/packages/platform-server/test/server_styles_host_spec.ts b/packages/platform-server/test/server_styles_host_spec.ts new file mode 100644 index 0000000000..5a0056fb13 --- /dev/null +++ b/packages/platform-server/test/server_styles_host_spec.ts @@ -0,0 +1,50 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ɵgetDOM as getDOM} from '@angular/common'; +import {ServerStylesHost} from '@angular/platform-server/src/styles_host'; + + +(function() { +if (getDOM().supportsDOMEvents()) return; // NODE only + +describe('ServerStylesHost', () => { + let ssh: ServerStylesHost; + let documentHead: Element; + beforeEach(() => { + const doc = getDOM().createHtmlDocument(); + ssh = new ServerStylesHost(doc, ''); + documentHead = doc.head; + doc.querySelector('title')?.remove(); + }); + + it('should add existing styles', () => { + ssh.addStyles(['a {};']); + expect(documentHead.innerHTML).toEqual(''); + }); + + it('should add new styles to hosts', () => { + ssh.addStyles(['a {};']); + expect(documentHead.innerHTML).toEqual(''); + }); + + it('should add styles only once to hosts', () => { + ssh.addStyles(['a {};']); + ssh.addStyles(['a {};']); + expect(documentHead.innerHTML).toEqual(''); + }); + + it('should remove style nodes on destroy', () => { + ssh.addStyles(['a {};']); + expect(documentHead.innerHTML).toEqual(''); + + ssh.ngOnDestroy(); + expect(documentHead.innerHTML).toEqual(''); + }); +}); +})();