Skip to content

Commit 108cce3

Browse files
authored
fix(cdk/overlay): Remove use of zone onStable to detach content (#28740)
1 parent 3fa56df commit 108cce3

File tree

1 file changed

+24
-3
lines changed

1 file changed

+24
-3
lines changed

src/cdk/overlay/overlay-ref.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import {
1414
EnvironmentInjector,
1515
NgZone,
1616
afterNextRender,
17+
afterRender,
18+
untracked,
19+
AfterRenderRef,
1720
} from '@angular/core';
1821
import {Location} from '@angular/common';
1922
import {Observable, Subject, merge, SubscriptionLike, Subscription} from 'rxjs';
@@ -60,6 +63,10 @@ export class OverlayRef implements PortalOutlet {
6063
/** Stream of mouse outside events dispatched to this overlay. */
6164
readonly _outsidePointerEvents = new Subject<MouseEvent>();
6265

66+
private _renders = new Subject<void>();
67+
68+
private _afterRenderRef: AfterRenderRef;
69+
6370
constructor(
6471
private _portalOutlet: PortalOutlet,
6572
private _host: HTMLElement,
@@ -79,6 +86,18 @@ export class OverlayRef implements PortalOutlet {
7986
}
8087

8188
this._positionStrategy = _config.positionStrategy;
89+
90+
// Users could open the overlay from an `effect`, in which case we need to
91+
// run the `afterRender` as `untracked`. We don't recommend that users do
92+
// this, but we also don't want to break users who are doing it.
93+
this._afterRenderRef = untracked(() =>
94+
afterRender(
95+
() => {
96+
this._renders.next();
97+
},
98+
{injector: this._injector},
99+
),
100+
);
82101
}
83102

84103
/** The overlay's HTML element */
@@ -223,7 +242,7 @@ export class OverlayRef implements PortalOutlet {
223242

224243
// Keeping the host element in the DOM can cause scroll jank, because it still gets
225244
// rendered, even though it's transparent and unclickable which is why we remove it.
226-
this._detachContentWhenStable();
245+
this._detachContentWhenEmpty();
227246
this._locationChanges.unsubscribe();
228247
this._outsideClickDispatcher.remove(this);
229248
return detachmentResult;
@@ -256,6 +275,8 @@ export class OverlayRef implements PortalOutlet {
256275
}
257276

258277
this._detachments.complete();
278+
this._afterRenderRef.destroy();
279+
this._renders.complete();
259280
}
260281

261282
/** Whether the overlay has attached content. */
@@ -491,15 +512,15 @@ export class OverlayRef implements PortalOutlet {
491512
}
492513

493514
/** Detaches the overlay content next time the zone stabilizes. */
494-
private _detachContentWhenStable() {
515+
private _detachContentWhenEmpty() {
495516
// Normally we wouldn't have to explicitly run this outside the `NgZone`, however
496517
// if the consumer is using `zone-patch-rxjs`, the `Subscription.unsubscribe` call will
497518
// be patched to run inside the zone, which will throw us into an infinite loop.
498519
this._ngZone.runOutsideAngular(() => {
499520
// We can't remove the host here immediately, because the overlay pane's content
500521
// might still be animating. This stream helps us avoid interrupting the animation
501522
// by waiting for the pane to become empty.
502-
const subscription = this._ngZone.onStable
523+
const subscription = this._renders
503524
.pipe(takeUntil(merge(this._attachments, this._detachments)))
504525
.subscribe(() => {
505526
// Needs a couple of checks for the pane and host, because

0 commit comments

Comments
 (0)