From 981d740fbb9734b01b53da4db79e2bb7a967371d Mon Sep 17 00:00:00 2001 From: crisbeto Date: Wed, 18 Jul 2018 22:09:05 +0200 Subject: [PATCH] perf(table): leaking reference through mostRecentCellOutlet Fixes the table leaking out a reference to a cell outlet via the `CdkCellOutlet.mostRecentCellOutlet` after all tables have been destroyed. Fixes #12259. --- src/cdk/table/row.ts | 11 ++++++++++- src/cdk/table/table.spec.ts | 13 ++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/cdk/table/row.ts b/src/cdk/table/row.ts index 6b5363f62c92..d9cf19517f3c 100644 --- a/src/cdk/table/row.ts +++ b/src/cdk/table/row.ts @@ -14,6 +14,7 @@ import { IterableDiffer, IterableDiffers, OnChanges, + OnDestroy, SimpleChanges, TemplateRef, ViewContainerRef, @@ -207,7 +208,7 @@ export interface CdkCellOutletMultiRowContext { * @docs-private */ @Directive({selector: '[cdkCellOutlet]'}) -export class CdkCellOutlet { +export class CdkCellOutlet implements OnDestroy { /** The ordered list of cells to render within this outlet's view container */ cells: CdkCellDef[]; @@ -226,6 +227,14 @@ export class CdkCellOutlet { constructor(public _viewContainer: ViewContainerRef) { CdkCellOutlet.mostRecentCellOutlet = this; } + + ngOnDestroy() { + // If this was the last outlet being rendered in the view, remove the reference + // from the static property after it has been destroyed to avoid leaking memory. + if (CdkCellOutlet.mostRecentCellOutlet === this) { + CdkCellOutlet.mostRecentCellOutlet = null; + } + } } /** Header template container that contains the cell outlet. Adds the right class and role. */ diff --git a/src/cdk/table/table.spec.ts b/src/cdk/table/table.spec.ts index fecc5783f907..7bd119f6b335 100644 --- a/src/cdk/table/table.spec.ts +++ b/src/cdk/table/table.spec.ts @@ -14,7 +14,7 @@ import {BehaviorSubject, combineLatest, Observable, of as observableOf} from 'rx import {map} from 'rxjs/operators'; import {CdkColumnDef} from './cell'; import {CdkTableModule} from './index'; -import {CdkHeaderRowDef, CdkRowDef} from './row'; +import {CdkHeaderRowDef, CdkRowDef, CdkCellOutlet} from './row'; import {CdkTable} from './table'; import { getTableDuplicateColumnNameError, @@ -137,6 +137,17 @@ describe('CdkTable', () => { }); }); + it('should clear the `mostRecentCellOutlet` on destroy', () => { + // Note: we cast the assertions here to booleans, because they may + // contain circular objects which will throw Jasmine into an infinite + // when its tries to stringify them to show a test failure. + expect(!!CdkCellOutlet.mostRecentCellOutlet).toBe(true); + + fixture.destroy(); + + expect(!!CdkCellOutlet.mostRecentCellOutlet).toBe(false); + }); + describe('should correctly use the differ to add/remove/move rows', () => { function addInitialIndexAttribute() { // Each row receives an attribute 'initialIndex' the element's original place