Skip to content

Commit c2e502c

Browse files
crisbetojelbourn
authored andcommitted
fix(overlay): infinite loop when used together with zone-patch-rxjs (#13081)
Fixes the overlay causing the browser to go into an infinite loop if the consumer is using `zone-patch-rxjs`. Fixes #12686.
1 parent 5c4a334 commit c2e502c

File tree

1 file changed

+30
-19
lines changed

1 file changed

+30
-19
lines changed

src/cdk/overlay/overlay-ref.ts

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -186,25 +186,9 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
186186
// Remove this overlay from keyboard dispatcher tracking.
187187
this._keyboardDispatcher.remove(this);
188188

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-
});
189+
// Keeping the host element in DOM the can cause scroll jank, because it still gets
190+
// rendered, even though it's transparent and unclickable which is why we remove it.
191+
this._detachContentWhenStable();
208192

209193
return detachmentResult;
210194
}
@@ -425,6 +409,33 @@ export class OverlayRef implements PortalOutlet, OverlayReference {
425409
isAdd ? classList.add(cssClass) : classList.remove(cssClass);
426410
});
427411
}
412+
413+
/** Detaches the overlay content next time the zone stabilizes. */
414+
private _detachContentWhenStable() {
415+
// Normally we wouldn't have to explicitly run this outside the `NgZone`, however
416+
// if the consumer is using `zone-patch-rxjs`, the `Subscription.unsubscribe` call will
417+
// be patched to run inside the zone, which will throw us into an infinite loop.
418+
this._ngZone.runOutsideAngular(() => {
419+
// We can't remove the host here immediately, because the overlay pane's content
420+
// might still be animating. This stream helps us avoid interrupting the animation
421+
// by waiting for the pane to become empty.
422+
const subscription = this._ngZone.onStable
423+
.asObservable()
424+
.pipe(takeUntil(merge(this._attachments, this._detachments)))
425+
.subscribe(() => {
426+
// Needs a couple of checks for the pane and host, because
427+
// they may have been removed by the time the zone stabilizes.
428+
if (!this._pane || !this._host || this._pane.children.length === 0) {
429+
if (this._host && this._host.parentElement) {
430+
this._previousHostParent = this._host.parentElement;
431+
this._previousHostParent.removeChild(this._host);
432+
}
433+
434+
subscription.unsubscribe();
435+
}
436+
});
437+
});
438+
}
428439
}
429440

430441

0 commit comments

Comments
 (0)