angular/components · error · Error
Scroll strategy has already been attached.
Error message
Scroll strategy has already been attached.
What it means
RepositionScrollStrategy, like CloseScrollStrategy, allows only one attached OverlayRef at a time; a second `attach` call while `_overlayRef` is still set throws `getMatScrollStrategyAlreadyAttachedError()`. This prevents scroll events from repositioning multiple unrelated overlays.
Source
Thrown at src/cdk/overlay/scroll/reposition-scroll-strategy.ts:61
/**
* Strategy that will update the element position as the user is scrolling.
*/
export class RepositionScrollStrategy implements ScrollStrategy {
private _scrollSubscription: Subscription | null = null;
private _overlayRef!: OverlayRef;
constructor(
private _scrollDispatcher: ScrollDispatcher,
private _viewportRuler: ViewportRuler,
private _ngZone: NgZone,
private _config?: RepositionScrollStrategyConfig,
) {}
/** Attaches this scroll strategy to an overlay. */
attach(overlayRef: OverlayRef) {
if (this._overlayRef && (typeof ngDevMode === 'undefined' || ngDevMode)) {
throw getMatScrollStrategyAlreadyAttachedError();
}
this._overlayRef = overlayRef;
}
/** Enables repositioning of the attached overlay on scroll. */
enable() {
if (!this._scrollSubscription) {
const throttle = this._config ? this._config.scrollThrottle : 0;
this._scrollSubscription = this._scrollDispatcher.scrolled(throttle).subscribe(() => {
this._overlayRef.updatePosition();
// TODO(crisbeto): make `close` on by default once all components can handle it.
if (this._config && this._config.autoClose) {
const overlayRect = this._overlayRef.overlayElement.getBoundingClientRect();
const {width, height} = this._viewportRuler.getViewportSize();
View on GitHub (pinned to 0411926e7d)
Solutions
- Request a new strategy per overlay via `overlay.scrollStrategies.reposition({...})` at open time
- Detach the existing overlay (`overlayRef.detach()`) before reusing the strategy
- Refactor shared services to expose a factory function returning a new strategy per call
- If attaching manually, set the strategy's `_overlayRef` back to null by detaching from the old overlay first
Example fix
// before
readonly strategy = this.overlay.scrollStrategies.reposition({autoClose: true});
attachTo(ref) { this.strategy.attach(ref); } // second call throws
// after
attachTo(ref) { this.overlay.scrollStrategies.reposition({autoClose: true}).attach(ref); } Defensive patterns
Strategy: validation
Validate before calling
function canAttachReposition(strategy: unknown): boolean {
return (strategy as {_overlayRef?: unknown})._overlayRef == null;
}
if (canAttachReposition(this.strategy)) { this.strategy.attach(ref); } Type guard
function isAttachable(s: RepositionScrollStrategy): boolean {
return (s as unknown as {_overlayRef?: OverlayRef | null})._overlayRef == null;
} Try / catch
try {
strategy.attach(overlayRef);
} catch (e) {
if (e.message.includes('Scroll strategy has already been attached')) {
(strategy as any)._overlayRef = null; // or detach old overlay
strategy.attach(overlayRef);
} else { throw e; }
} Prevention
- Create a new reposition strategy for each overlay via scrollStrategies.reposition()
- Avoid singleton strategy instances in providers
- Track which overlay a strategy is attached to in your own state
- Let MatDialog/Overlay manage strategy lifecycle where possible
When it happens
Trigger: Reusing one `scrollStrategies.reposition()` result (or a custom strategy built once in a service) to attach to a second overlay while the first attachment is still active.
Common situations: A shared strategy singleton passed to several dropdowns/tooltips; a custom `MatDialogConfig.scrollStrategy` cached in a service and reused across dialog opens; wrapping reposition strategies in a provider that constructs once.
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/c74b046a988be02c.
Report an issue: GitHub.