angular/components · error · Error
Scroll strategy has already been attached.
Error message
Scroll strategy has already been attached.
What it means
CloseScrollStrategy tracks a single attached OverlayRef; calling `attach` a second time without detaching throws `getMatScrollStrategyAlreadyAttachedError()`. The strategy is designed to be bound to exactly one overlay at a time so scroll-closing behavior is unambiguous.
Source
Thrown at src/cdk/overlay/scroll/close-scroll-strategy.ts:58
/**
* Strategy that will close the overlay as soon as the user starts scrolling.
*/
export class CloseScrollStrategy implements ScrollStrategy {
private _scrollSubscription: Subscription | null = null;
private _overlayRef!: OverlayRef;
private _initialScrollPosition!: number;
constructor(
private _scrollDispatcher: ScrollDispatcher,
private _ngZone: NgZone,
private _viewportRuler: ViewportRuler,
private _config?: CloseScrollStrategyConfig,
) {}
/** Attaches this scroll strategy to an overlay. */
attach(overlayRef: OverlayRef) {
if (this._overlayRef && (typeof ngDevMode === 'undefined' || ngDevMode)) {
throw getMatScrollStrategyAlreadyAttachedError();
}
this._overlayRef = overlayRef;
}
/** Enables the closing of the attached overlay on scroll. */
enable() {
if (this._scrollSubscription) {
return;
}
const stream = this._scrollDispatcher.scrolled(0).pipe(
filter(scrollable => {
return (
!scrollable ||
!this._overlayRef.overlayElement.contains(scrollable.getElementRef().nativeElement)
);
}),View on GitHub (pinned to 0411926e7d)
Solutions
- Create a new CloseScrollStrategy per overlay: `overlay.scrollStrategies.close()` returns a fresh strategy each call
- Detach the strategy from the previous overlay before attaching it to a new one
- If sharing in a service, store the strategy factory (function) instead of a strategy instance
- Call `overlayRef.detach()`/dispose the prior overlay so the strategy can be reused
Example fix
// before
private readonly scrollStrategy = this.overlay.scrollStrategies.close();
open() { this.dialog.open(D, {scrollStrategy: this.scrollStrategy}); } // second open throws
// after
open() { this.dialog.open(D, {scrollStrategy: this.overlay.scrollStrategies.close()}); } // fresh strategy per open Defensive patterns
Strategy: validation
Validate before calling
import {CloseScrollStrategy} from '@angular/cdk/overlay';
function isFresh(strategy: CloseScrollStrategy): boolean {
return (strategy as any)._overlayRef == null;
} Type guard
function canAttach(strategy: CloseScrollStrategy): boolean {
return (strategy as unknown as {_overlayRef?: unknown})._overlayRef === undefined;
} Try / catch
try {
strategy.attach(overlayRef);
} catch (e) {
if (e.message.includes('Scroll strategy has already been attached')) {
strategy = overlay.scrollStrategies.close(); // fresh strategy
strategy.attach(overlayRef);
} else { throw e; }
} Prevention
- Never cache scroll strategy instances in services; call scrollStrategies.close() per overlay
- Use MatDialog/Overlay config scrollStrategy factories instead of shared instances
- Detach the previous overlay before reusing a strategy
- If you must reuse, null out _overlayRef via detach logic
When it happens
Trigger: Reusing a single CloseScrollStrategy instance (e.g. one created via a shared ScrollStrategyFactory or stored in a service) to attach to a second overlay without detaching the first or creating a new strategy.
Common situations: Passing `this.scrollStrategy` (a memoized singleton) to multiple dialogs/overlays; opening two overlays that share one strategy from a provider; re-opening an overlay after it was closed without detaching its strategy.
Related errors
- Scroll strategy has already been attached.
- ConnectedPosition: Invalid ${property} "${value}". Expected
- ConnectedPosition: Invalid ${property} "${value}". Expected
- This position strategy is already attached to an overlay
- FlexibleConnectedPositionStrategy: At least one position is
AI-assisted analysis of angular/components@0411926e7d (2026-08-31).
Data as JSON: /api/errors/514bddd98eeff33a.
Report an issue: GitHub.