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

  1. Request a new strategy per overlay via `overlay.scrollStrategies.reposition({...})` at open time
  2. Detach the existing overlay (`overlayRef.detach()`) before reusing the strategy
  3. Refactor shared services to expose a factory function returning a new strategy per call
  4. 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

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


AI-assisted analysis of angular/components@0411926e7d (2026-08-31). Data as JSON: /api/errors/c74b046a988be02c. Report an issue: GitHub.