Skip to content

Commit 24c2567

Browse files
authored
fix(material/select): overlay not detached on time after exit animation in some cases (#30456)
Fixes an issue that was reported internally where in some setups the select would animate away, but wouldn't be detached after the exit animation until the next change detection.
1 parent 3d91f61 commit 24c2567

File tree

1 file changed

+29
-25
lines changed

1 file changed

+29
-25
lines changed

src/material/select/select.ts

+29-25
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ import {
6161
HostAttributeToken,
6262
ANIMATION_MODULE_TYPE,
6363
Renderer2,
64-
NgZone,
6564
} from '@angular/core';
6665
import {
6766
AbstractControl,
@@ -216,7 +215,6 @@ export class MatSelect
216215
private _dir = inject(Directionality, {optional: true});
217216
private _idGenerator = inject(_IdGenerator);
218217
private _renderer = inject(Renderer2);
219-
private _ngZone = inject(NgZone);
220218
protected _parentFormField = inject<MatFormField>(MAT_FORM_FIELD, {optional: true});
221219
ngControl = inject(NgControl, {self: true, optional: true})!;
222220
private _liveAnnouncer = inject(LiveAnnouncer);
@@ -845,35 +843,41 @@ export class MatSelect
845843
/** Triggers the exit animation and detaches the overlay at the end. */
846844
private _exitAndDetach() {
847845
if (this._animationsDisabled || !this.panel) {
848-
this._overlayDir.detachOverlay();
846+
this._detachOverlay();
849847
return;
850848
}
851849

852-
this._ngZone.runOutsideAngular(() => {
853-
this._cleanupDetach?.();
854-
this._cleanupDetach = () => {
855-
cleanupEvent();
856-
clearTimeout(exitFallbackTimer);
857-
this._cleanupDetach = undefined;
858-
};
850+
this._cleanupDetach?.();
851+
this._cleanupDetach = () => {
852+
cleanupEvent();
853+
clearTimeout(exitFallbackTimer);
854+
this._cleanupDetach = undefined;
855+
};
856+
857+
const panel: HTMLElement = this.panel.nativeElement;
858+
const cleanupEvent = this._renderer.listen(panel, 'animationend', (event: AnimationEvent) => {
859+
if (event.animationName === '_mat-select-exit') {
860+
this._cleanupDetach?.();
861+
this._detachOverlay();
862+
}
863+
});
859864

860-
const panel: HTMLElement = this.panel.nativeElement;
861-
const cleanupEvent = this._renderer.listen(panel, 'animationend', (event: AnimationEvent) => {
862-
if (event.animationName === '_mat-select-exit') {
863-
this._cleanupDetach?.();
864-
this._overlayDir.detachOverlay();
865-
}
866-
});
865+
// Since closing the overlay depends on the animation, we have a fallback in case the panel
866+
// doesn't animate. This can happen in some internal tests that do `* {animation: none}`.
867+
const exitFallbackTimer = setTimeout(() => {
868+
this._cleanupDetach?.();
869+
this._detachOverlay();
870+
}, 200);
867871

868-
// Since closing the overlay depends on the animation, we have a fallback in case the panel
869-
// doesn't animate. This can happen in some internal tests that do `* {animation: none}`.
870-
const exitFallbackTimer = setTimeout(() => {
871-
this._cleanupDetach?.();
872-
this._overlayDir.detachOverlay();
873-
}, 200);
872+
panel.classList.add('mat-select-panel-exit');
873+
}
874874

875-
panel.classList.add('mat-select-panel-exit');
876-
});
875+
/** Detaches the current overlay directive. */
876+
private _detachOverlay() {
877+
this._overlayDir.detachOverlay();
878+
// Some of the overlay detachment logic depends on change detection.
879+
// Mark for check to ensure that things get picked up in a timely manner.
880+
this._changeDetectorRef.markForCheck();
877881
}
878882

879883
/**

0 commit comments

Comments
 (0)