{"record":{"id":"c74b046a988be02c","repo":"angular/components","slug":"scroll-strategy-has-already-been-attached-c74b04","errorCode":null,"errorMessage":"Scroll strategy has already been attached.","messagePattern":"Scroll strategy has already been attached\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/cdk/overlay/scroll/reposition-scroll-strategy.ts","lineNumber":61,"sourceCode":"\n/**\n * Strategy that will update the element position as the user is scrolling.\n */\nexport class RepositionScrollStrategy implements ScrollStrategy {\n  private _scrollSubscription: Subscription | null = null;\n  private _overlayRef!: OverlayRef;\n\n  constructor(\n    private _scrollDispatcher: ScrollDispatcher,\n    private _viewportRuler: ViewportRuler,\n    private _ngZone: NgZone,\n    private _config?: RepositionScrollStrategyConfig,\n  ) {}\n\n  /** Attaches this scroll strategy to an overlay. */\n  attach(overlayRef: OverlayRef) {\n    if (this._overlayRef && (typeof ngDevMode === 'undefined' || ngDevMode)) {\n      throw getMatScrollStrategyAlreadyAttachedError();\n    }\n\n    this._overlayRef = overlayRef;\n  }\n\n  /** Enables repositioning of the attached overlay on scroll. */\n  enable() {\n    if (!this._scrollSubscription) {\n      const throttle = this._config ? this._config.scrollThrottle : 0;\n\n      this._scrollSubscription = this._scrollDispatcher.scrolled(throttle).subscribe(() => {\n        this._overlayRef.updatePosition();\n\n        // TODO(crisbeto): make `close` on by default once all components can handle it.\n        if (this._config && this._config.autoClose) {\n          const overlayRect = this._overlayRef.overlayElement.getBoundingClientRect();\n          const {width, height} = this._viewportRuler.getViewportSize();\n","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/angular/components/blob/0411926e7d8ae06b32236ec1048a888cfad5abf2/src/cdk/overlay/scroll/reposition-scroll-strategy.ts#L43-L79","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nreadonly strategy = this.overlay.scrollStrategies.reposition({autoClose: true});\nattachTo(ref) { this.strategy.attach(ref); } // second call throws\n// after\nattachTo(ref) { this.overlay.scrollStrategies.reposition({autoClose: true}).attach(ref); }","handlingStrategy":"validation","validationCode":"function canAttachReposition(strategy: unknown): boolean {\n  return (strategy as {_overlayRef?: unknown})._overlayRef == null;\n}\nif (canAttachReposition(this.strategy)) { this.strategy.attach(ref); }","typeGuard":"function isAttachable(s: RepositionScrollStrategy): boolean {\n  return (s as unknown as {_overlayRef?: OverlayRef | null})._overlayRef == null;\n}","tryCatchPattern":"try {\n  strategy.attach(overlayRef);\n} catch (e) {\n  if (e.message.includes('Scroll strategy has already been attached')) {\n    (strategy as any)._overlayRef = null; // or detach old overlay\n    strategy.attach(overlayRef);\n  } else { throw e; }\n}","preventionTips":["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"],"tags":["angular","cdk","overlay","scroll-strategy"],"backgroundTag":"scroll-strategy-already-attached","analyzedSha":"0411926e7d8ae06b32236ec1048a888cfad5abf2","analyzedAt":"2026-08-31T11:58:23.400Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}