66 * found in the LICENSE file at https://angular.dev/license
77 */
88
9- import { Direction , Directionality } from '../bidi' ;
10- import { ComponentPortal , Portal , PortalOutlet , TemplatePortal } from '../portal' ;
9+ import { Location } from '@angular/common' ;
1110import {
1211 AfterRenderRef ,
1312 ComponentRef ,
@@ -16,19 +15,17 @@ import {
1615 NgZone ,
1716 Renderer2 ,
1817 afterNextRender ,
19- afterRender ,
20- untracked ,
2118} from '@angular/core' ;
22- import { Location } from '@angular/common' ;
23- import { Observable , Subject , merge , SubscriptionLike , Subscription } from 'rxjs' ;
24- import { takeUntil } from 'rxjs/operators' ;
19+ import { Observable , Subject , Subscription , SubscriptionLike } from 'rxjs' ;
20+ import { Direction , Directionality } from '../bidi' ;
21+ import { coerceArray , coerceCssPixelValue } from '../coercion' ;
22+ import { ComponentPortal , Portal , PortalOutlet , TemplatePortal } from '../portal' ;
23+ import { BackdropRef } from './backdrop-ref' ;
2524import { OverlayKeyboardDispatcher } from './dispatchers/overlay-keyboard-dispatcher' ;
2625import { OverlayOutsideClickDispatcher } from './dispatchers/overlay-outside-click-dispatcher' ;
2726import { OverlayConfig } from './overlay-config' ;
28- import { coerceCssPixelValue , coerceArray } from '../coercion' ;
2927import { PositionStrategy } from './position/position-strategy' ;
3028import { ScrollStrategy } from './scroll' ;
31- import { BackdropRef } from './backdrop-ref' ;
3229
3330/** An object where all of its properties cannot be written. */
3431export type ImmutableObject < T > = {
@@ -47,6 +44,8 @@ export class OverlayRef implements PortalOutlet {
4744 private _scrollStrategy : ScrollStrategy | undefined ;
4845 private _locationChanges : SubscriptionLike = Subscription . EMPTY ;
4946 private _backdropRef : BackdropRef | null = null ;
47+ private _detachContentMutationObserver : MutationObserver | undefined ;
48+ private _detachContentAfterRenderRef : AfterRenderRef | undefined ;
5049
5150 /**
5251 * Reference to the parent of the `_host` at the time it was detached. Used to restore
@@ -60,10 +59,6 @@ export class OverlayRef implements PortalOutlet {
6059 /** Stream of mouse outside events dispatched to this overlay. */
6160 readonly _outsidePointerEvents = new Subject < MouseEvent > ( ) ;
6261
63- private _renders = new Subject < void > ( ) ;
64-
65- private _afterRenderRef : AfterRenderRef ;
66-
6762 /** Reference to the currently-running `afterNextRender` call. */
6863 private _afterNextRenderRef : AfterRenderRef | undefined ;
6964
@@ -87,18 +82,6 @@ export class OverlayRef implements PortalOutlet {
8782 }
8883
8984 this . _positionStrategy = _config . positionStrategy ;
90-
91- // Users could open the overlay from an `effect`, in which case we need to
92- // run the `afterRender` as `untracked`. We don't recommend that users do
93- // this, but we also don't want to break users who are doing it.
94- this . _afterRenderRef = untracked ( ( ) =>
95- afterRender (
96- ( ) => {
97- this . _renders . next ( ) ;
98- } ,
99- { injector : this . _injector } ,
100- ) ,
101- ) ;
10285 }
10386
10487 /** The overlay's HTML element */
@@ -182,6 +165,7 @@ export class OverlayRef implements PortalOutlet {
182165
183166 // Only emit the `attachments` event once all other setup is done.
184167 this . _attachments . next ( ) ;
168+ this . _completeDetachContent ( ) ;
185169
186170 // Track this overlay by the keyboard dispatcher
187171 this . _keyboardDispatcher . add ( this ) ;
@@ -242,6 +226,7 @@ export class OverlayRef implements PortalOutlet {
242226
243227 // Only emit after everything is detached.
244228 this . _detachments . next ( ) ;
229+ this . _completeDetachContent ( ) ;
245230
246231 // Remove this overlay from keyboard dispatcher tracking.
247232 this . _keyboardDispatcher . remove ( this ) ;
@@ -281,8 +266,7 @@ export class OverlayRef implements PortalOutlet {
281266 }
282267
283268 this . _detachments . complete ( ) ;
284- this . _afterRenderRef . destroy ( ) ;
285- this . _renders . complete ( ) ;
269+ this . _completeDetachContent ( ) ;
286270 }
287271
288272 /** Whether the overlay has attached content. */
@@ -488,34 +472,42 @@ export class OverlayRef implements PortalOutlet {
488472 }
489473 }
490474
491- /** Detaches the overlay content next time the zone stabilizes . */
475+ /** Detaches the overlay once the content finishes animating and is removed from the DOM . */
492476 private _detachContentWhenEmpty ( ) {
493- // Normally we wouldn't have to explicitly run this outside the `NgZone`, however
494- // if the consumer is using `zone-patch-rxjs`, the `Subscription.unsubscribe` call will
495- // be patched to run inside the zone, which will throw us into an infinite loop.
496- this . _ngZone . runOutsideAngular ( ( ) => {
497- // We can't remove the host here immediately, because the overlay pane's content
498- // might still be animating. This stream helps us avoid interrupting the animation
499- // by waiting for the pane to become empty.
500- const subscription = this . _renders
501- . pipe ( takeUntil ( merge ( this . _attachments , this . _detachments ) ) )
502- . subscribe ( ( ) => {
503- // Needs a couple of checks for the pane and host, because
504- // they may have been removed by the time the zone stabilizes.
505- if ( ! this . _pane || ! this . _host || this . _pane . children . length === 0 ) {
506- if ( this . _pane && this . _config . panelClass ) {
507- this . _toggleClasses ( this . _pane , this . _config . panelClass , false ) ;
508- }
509-
510- if ( this . _host && this . _host . parentElement ) {
511- this . _previousHostParent = this . _host . parentElement ;
512- this . _host . remove ( ) ;
513- }
514-
515- subscription . unsubscribe ( ) ;
516- }
517- } ) ;
477+ // Attempt to detach on the next render.
478+ this . _detachContentAfterRenderRef = afterNextRender ( ( ) => this . _detachContent ( ) , {
479+ injector : this . _injector ,
518480 } ) ;
481+ // Otherwise wait until the content finishes animating out and detach.
482+ if ( globalThis . MutationObserver && this . _pane ) {
483+ this . _detachContentMutationObserver ||= new globalThis . MutationObserver ( ( ) => {
484+ this . _detachContent ( ) ;
485+ } ) ;
486+ this . _detachContentMutationObserver . observe ( this . _pane , { childList : true } ) ;
487+ }
488+ }
489+
490+ private _detachContent ( ) {
491+ // Needs a couple of checks for the pane and host, because
492+ // they may have been removed by the time the zone stabilizes.
493+ if ( ! this . _pane || ! this . _host || this . _pane . children . length === 0 ) {
494+ if ( this . _pane && this . _config . panelClass ) {
495+ this . _toggleClasses ( this . _pane , this . _config . panelClass , false ) ;
496+ }
497+
498+ if ( this . _host && this . _host . parentElement ) {
499+ this . _previousHostParent = this . _host . parentElement ;
500+ this . _host . remove ( ) ;
501+ }
502+
503+ this . _completeDetachContent ( ) ;
504+ }
505+ }
506+
507+ private _completeDetachContent ( ) {
508+ this . _detachContentAfterRenderRef ?. destroy ( ) ;
509+ this . _detachContentAfterRenderRef = undefined ;
510+ this . _detachContentMutationObserver ?. disconnect ( ) ;
519511 }
520512
521513 /** Disposes of a scroll strategy. */
0 commit comments