Skip to content

Commit bf94082

Browse files
authored
fix(cdk/table): memory leak when no rows are sticky (angular#30461)
The table has some logic that queues up measurements of rows that will become sticky so that they can be measured once we set up the resize observer. Afterwards the queue is cleared once the measurements are done. angular#29814 introduced a memory leak where the tracking was happening even if the row isn't actually sticky which meant that the resize observer was never set up and the queue kept growing as new rows are rendered. These changes resolve the leak by only queuing the measurement if it's necessary. I've also fixed another potential leak where we were setting up the resize observer, but we weren't destroying it. Fixes angular#30453.
1 parent 622152d commit bf94082

File tree

1 file changed

+15
-18
lines changed

1 file changed

+15
-18
lines changed

src/cdk/table/sticky-styler.ts

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class StickyStyler {
3939
? new globalThis.ResizeObserver(entries => this._updateCachedSizes(entries))
4040
: null;
4141
private _updatedStickyColumnsParamsToReplay: UpdateStickyColumnsParams[] = [];
42-
private _stickyColumnsReplayTimeout: number | null = null;
42+
private _stickyColumnsReplayTimeout: ReturnType<typeof setTimeout> | null = null;
4343
private _cachedCellWidths: number[] = [];
4444
private readonly _borderCellCss: Readonly<{[d in StickyDirection]: string}>;
4545
private _destroyed = false;
@@ -128,24 +128,14 @@ export class StickyStyler {
128128
recalculateCellWidths = true,
129129
replay = true,
130130
) {
131-
if (replay) {
132-
this._updateStickyColumnReplayQueue({
133-
rows: [...rows],
134-
stickyStartStates: [...stickyStartStates],
135-
stickyEndStates: [...stickyEndStates],
136-
});
137-
}
138-
131+
// Don't cache any state if none of the columns are sticky.
139132
if (
140133
!rows.length ||
141134
!this._isBrowser ||
142135
!(stickyStartStates.some(state => state) || stickyEndStates.some(state => state))
143136
) {
144-
if (this._positionListener) {
145-
this._positionListener.stickyColumnsUpdated({sizes: []});
146-
this._positionListener.stickyEndColumnsUpdated({sizes: []});
147-
}
148-
137+
this._positionListener?.stickyColumnsUpdated({sizes: []});
138+
this._positionListener?.stickyEndColumnsUpdated({sizes: []});
149139
return;
150140
}
151141

@@ -164,6 +154,14 @@ export class StickyStyler {
164154
let startPositions: number[];
165155
let endPositions: number[];
166156

157+
if (replay) {
158+
this._updateStickyColumnReplayQueue({
159+
rows: [...rows],
160+
stickyStartStates: [...stickyStartStates],
161+
stickyEndStates: [...stickyEndStates],
162+
});
163+
}
164+
167165
this._afterNextRender({
168166
earlyRead: () => {
169167
cellWidths = this._getCellWidths(firstRow, recalculateCellWidths);
@@ -321,6 +319,7 @@ export class StickyStyler {
321319
clearTimeout(this._stickyColumnsReplayTimeout);
322320
}
323321

322+
this._resizeObserver?.disconnect();
324323
this._destroyed = true;
325324
}
326325

@@ -493,11 +492,9 @@ export class StickyStyler {
493492
this._removeFromStickyColumnReplayQueue(params.rows);
494493

495494
// No need to replay if a flush is pending.
496-
if (this._stickyColumnsReplayTimeout) {
497-
return;
495+
if (!this._stickyColumnsReplayTimeout) {
496+
this._updatedStickyColumnsParamsToReplay.push(params);
498497
}
499-
500-
this._updatedStickyColumnsParamsToReplay.push(params);
501498
}
502499

503500
/** Remove updates for the specified rows from the queue. */

0 commit comments

Comments
 (0)