99import { Direction , Directionality } from '@angular/cdk/bidi' ;
1010import { ComponentPortal , Portal , PortalOutlet , TemplatePortal } from '@angular/cdk/portal' ;
1111import { ComponentRef , EmbeddedViewRef , NgZone } from '@angular/core' ;
12- import { Observable , Subject } from 'rxjs' ;
13- import { take } from 'rxjs/operators' ;
12+ import { Observable , Subject , merge } from 'rxjs' ;
13+ import { take , takeUntil } from 'rxjs/operators' ;
1414import { OverlayKeyboardDispatcher } from './keyboard/overlay-keyboard-dispatcher' ;
1515import { OverlayConfig } from './overlay-config' ;
1616import { coerceCssPixelValue , coerceArray } from '@angular/cdk/coercion' ;
@@ -31,6 +31,12 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
3131 private _backdropClick : Subject < MouseEvent > = new Subject ( ) ;
3232 private _attachments = new Subject < void > ( ) ;
3333 private _detachments = new Subject < void > ( ) ;
34+
35+ /**
36+ * Reference to the parent of the `_host` at the time it was detached. Used to restore
37+ * the `_host` to its original position in the DOM when it gets re-attached.
38+ */
39+ private _previousHostParent : HTMLElement ;
3440 private _keydownEventsObservable : Observable < KeyboardEvent > = Observable . create ( observer => {
3541 const subscription = this . _keydownEvents . subscribe ( observer ) ;
3642 this . _keydownEventSubscriptions ++ ;
@@ -99,6 +105,10 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
99105 }
100106
101107 // Update the pane element with the given configuration.
108+ if ( ! this . _host . parentElement && this . _previousHostParent ) {
109+ this . _previousHostParent . appendChild ( this . _host ) ;
110+ }
111+
102112 this . _updateStackingOrder ( ) ;
103113 this . _updateElementSize ( ) ;
104114 this . _updateElementDirection ( ) ;
@@ -176,6 +186,26 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
176186 // Remove this overlay from keyboard dispatcher tracking.
177187 this . _keyboardDispatcher . remove ( this ) ;
178188
189+ // Keeping the host element in DOM the can cause scroll jank, because it still gets rendered,
190+ // even though it's transparent and unclickable. We can't remove the host here immediately,
191+ // because the overlay pane's content might still be animating. This stream helps us avoid
192+ // interrupting the animation by waiting for the pane to become empty.
193+ const subscription = this . _ngZone . onStable
194+ . asObservable ( )
195+ . pipe ( takeUntil ( merge ( this . _attachments , this . _detachments ) ) )
196+ . subscribe ( ( ) => {
197+ // Needs a couple of checks for the pane and host, because
198+ // they may have been removed by the time the zone stabilizes.
199+ if ( ! this . _pane || ! this . _host || this . _pane . children . length === 0 ) {
200+ if ( this . _host && this . _host . parentElement ) {
201+ this . _previousHostParent = this . _host . parentElement ;
202+ this . _previousHostParent . removeChild ( this . _host ) ;
203+ }
204+
205+ subscription . unsubscribe ( ) ;
206+ }
207+ } ) ;
208+
179209 return detachmentResult ;
180210 }
181211
@@ -203,7 +233,7 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
203233 this . _host = null ! ;
204234 }
205235
206- this . _pane = null ! ;
236+ this . _previousHostParent = this . _pane = null ! ;
207237
208238 if ( isAttached ) {
209239 this . _detachments . next ( ) ;
0 commit comments