@@ -14,6 +14,9 @@ import {
1414 EnvironmentInjector ,
1515 NgZone ,
1616 afterNextRender ,
17+ afterRender ,
18+ untracked ,
19+ AfterRenderRef ,
1720} from '@angular/core' ;
1821import { Location } from '@angular/common' ;
1922import { 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