Skip to content

Commit 7cc0d3a

Browse files
committed
fix(cdk/overlay): avoid leaking memory through afterNextRender (#29709)
The `OverlayRef` was triggering an `afterEachRender` and passing in an `EnvironmentInjector`. Under the hood this uses a `DestroyRef` that is never destroyed, because the `EnvironmentInjector` is almost never destroyed. These changes add an explicit `destroy` call to avoid the issue. Fixes #29696. (cherry picked from commit 3a62ab1)
1 parent 0e6dee3 commit 7cc0d3a

File tree

1 file changed

+12
-3
lines changed

1 file changed

+12
-3
lines changed

src/cdk/overlay/overlay-ref.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
import {Direction, Directionality} from '@angular/cdk/bidi';
1010
import {ComponentPortal, Portal, PortalOutlet, TemplatePortal} from '@angular/cdk/portal';
1111
import {
12+
AfterRenderRef,
1213
ComponentRef,
1314
EmbeddedViewRef,
1415
EnvironmentInjector,
1516
NgZone,
1617
afterNextRender,
1718
afterRender,
1819
untracked,
19-
AfterRenderRef,
2020
} from '@angular/core';
2121
import {Location} from '@angular/common';
2222
import {Observable, Subject, merge, SubscriptionLike, Subscription} from 'rxjs';
@@ -39,7 +39,7 @@ export type ImmutableObject<T> = {
3939
*/
4040
export class OverlayRef implements PortalOutlet {
4141
private _backdropElement: HTMLElement | null = null;
42-
private _backdropTimeout: number | undefined;
42+
private _backdropTimeout: ReturnType<typeof setTimeout> | undefined;
4343
private readonly _backdropClick = new Subject<MouseEvent>();
4444
private readonly _attachments = new Subject<void>();
4545
private readonly _detachments = new Subject<void>();
@@ -67,6 +67,9 @@ export class OverlayRef implements PortalOutlet {
6767

6868
private _afterRenderRef: AfterRenderRef;
6969

70+
/** Reference to the currently-running `afterNextRender` call. */
71+
private _afterNextRenderRef: AfterRenderRef | undefined;
72+
7073
constructor(
7174
private _portalOutlet: PortalOutlet,
7275
private _host: HTMLElement,
@@ -151,9 +154,14 @@ export class OverlayRef implements PortalOutlet {
151154
this._scrollStrategy.enable();
152155
}
153156

157+
// We need to clean this up ourselves, because we're passing in an
158+
// `EnvironmentInjector` below which won't ever be destroyed.
159+
// Otherwise it causes some callbacks to be retained (see #29696).
160+
this._afterNextRenderRef?.destroy();
161+
154162
// Update the position once the overlay is fully rendered before attempting to position it,
155163
// as the position may depend on the size of the rendered content.
156-
afterNextRender(
164+
this._afterNextRenderRef = afterNextRender(
157165
() => {
158166
// The overlay could've been detached before the callback executed.
159167
if (this.hasAttached()) {
@@ -267,6 +275,7 @@ export class OverlayRef implements PortalOutlet {
267275
this._outsidePointerEvents.complete();
268276
this._outsideClickDispatcher.remove(this);
269277
this._host?.remove();
278+
this._afterNextRenderRef?.destroy();
270279

271280
this._previousHostParent = this._pane = this._host = null!;
272281

0 commit comments

Comments
 (0)