Skip to content

Commit

Permalink
perf(table): leaking reference through mostRecentCellOutlet (#12269)
Browse files Browse the repository at this point in the history
Fixes the table leaking out a reference to a cell outlet via the `CdkCellOutlet.mostRecentCellOutlet` after all tables have been destroyed.

Fixes #12259.
  • Loading branch information
crisbeto authored and mmalerba committed Jul 30, 2018
1 parent 6661eff commit 7c8e892
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 2 deletions.
11 changes: 10 additions & 1 deletion src/cdk/table/row.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
IterableDiffer,
IterableDiffers,
OnChanges,
OnDestroy,
SimpleChanges,
TemplateRef,
ViewContainerRef,
Expand Down Expand Up @@ -207,7 +208,7 @@ export interface CdkCellOutletMultiRowContext<T> {
* @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[];

Expand All @@ -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. */
Expand Down
13 changes: 12 additions & 1 deletion src/cdk/table/table.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 7c8e892

Please sign in to comment.