Skip to content

Commit 7c8e892

Browse files
crisbetommalerba
authored andcommitted
perf(table): leaking reference through mostRecentCellOutlet (#12269)
Fixes the table leaking out a reference to a cell outlet via the `CdkCellOutlet.mostRecentCellOutlet` after all tables have been destroyed. Fixes #12259.
1 parent 6661eff commit 7c8e892

File tree

2 files changed

+22
-2
lines changed

2 files changed

+22
-2
lines changed

src/cdk/table/row.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
IterableDiffer,
1515
IterableDiffers,
1616
OnChanges,
17+
OnDestroy,
1718
SimpleChanges,
1819
TemplateRef,
1920
ViewContainerRef,
@@ -207,7 +208,7 @@ export interface CdkCellOutletMultiRowContext<T> {
207208
* @docs-private
208209
*/
209210
@Directive({selector: '[cdkCellOutlet]'})
210-
export class CdkCellOutlet {
211+
export class CdkCellOutlet implements OnDestroy {
211212
/** The ordered list of cells to render within this outlet's view container */
212213
cells: CdkCellDef[];
213214

@@ -226,6 +227,14 @@ export class CdkCellOutlet {
226227
constructor(public _viewContainer: ViewContainerRef) {
227228
CdkCellOutlet.mostRecentCellOutlet = this;
228229
}
230+
231+
ngOnDestroy() {
232+
// If this was the last outlet being rendered in the view, remove the reference
233+
// from the static property after it has been destroyed to avoid leaking memory.
234+
if (CdkCellOutlet.mostRecentCellOutlet === this) {
235+
CdkCellOutlet.mostRecentCellOutlet = null;
236+
}
237+
}
229238
}
230239

231240
/** Header template container that contains the cell outlet. Adds the right class and role. */

src/cdk/table/table.spec.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {BehaviorSubject, combineLatest, Observable, of as observableOf} from 'rx
1414
import {map} from 'rxjs/operators';
1515
import {CdkColumnDef} from './cell';
1616
import {CdkTableModule} from './index';
17-
import {CdkHeaderRowDef, CdkRowDef} from './row';
17+
import {CdkHeaderRowDef, CdkRowDef, CdkCellOutlet} from './row';
1818
import {CdkTable} from './table';
1919
import {
2020
getTableDuplicateColumnNameError,
@@ -137,6 +137,17 @@ describe('CdkTable', () => {
137137
});
138138
});
139139

140+
it('should clear the `mostRecentCellOutlet` on destroy', () => {
141+
// Note: we cast the assertions here to booleans, because they may
142+
// contain circular objects which will throw Jasmine into an infinite
143+
// when its tries to stringify them to show a test failure.
144+
expect(!!CdkCellOutlet.mostRecentCellOutlet).toBe(true);
145+
146+
fixture.destroy();
147+
148+
expect(!!CdkCellOutlet.mostRecentCellOutlet).toBe(false);
149+
});
150+
140151
describe('should correctly use the differ to add/remove/move rows', () => {
141152
function addInitialIndexAttribute() {
142153
// Each row receives an attribute 'initialIndex' the element's original place

0 commit comments

Comments
 (0)