{"record":{"id":"514bddd98eeff33a","repo":"angular/components","slug":"scroll-strategy-has-already-been-attached","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/close-scroll-strategy.ts","lineNumber":58,"sourceCode":"/**\n * Strategy that will close the overlay as soon as the user starts scrolling.\n */\nexport class CloseScrollStrategy implements ScrollStrategy {\n  private _scrollSubscription: Subscription | null = null;\n  private _overlayRef!: OverlayRef;\n  private _initialScrollPosition!: number;\n\n  constructor(\n    private _scrollDispatcher: ScrollDispatcher,\n    private _ngZone: NgZone,\n    private _viewportRuler: ViewportRuler,\n    private _config?: CloseScrollStrategyConfig,\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 the closing of the attached overlay on scroll. */\n  enable() {\n    if (this._scrollSubscription) {\n      return;\n    }\n\n    const stream = this._scrollDispatcher.scrolled(0).pipe(\n      filter(scrollable => {\n        return (\n          !scrollable ||\n          !this._overlayRef.overlayElement.contains(scrollable.getElementRef().nativeElement)\n        );\n      }),","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/angular/components/blob/0411926e7d8ae06b32236ec1048a888cfad5abf2/src/cdk/overlay/scroll/close-scroll-strategy.ts#L40-L76","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nprivate readonly scrollStrategy = this.overlay.scrollStrategies.close();\nopen() { this.dialog.open(D, {scrollStrategy: this.scrollStrategy}); } // second open throws\n// after\nopen() { this.dialog.open(D, {scrollStrategy: this.overlay.scrollStrategies.close()}); } // fresh strategy per open","handlingStrategy":"validation","validationCode":"import {CloseScrollStrategy} from '@angular/cdk/overlay';\nfunction isFresh(strategy: CloseScrollStrategy): boolean {\n  return (strategy as any)._overlayRef == null;\n}","typeGuard":"function canAttach(strategy: CloseScrollStrategy): boolean {\n  return (strategy as unknown as {_overlayRef?: unknown})._overlayRef === undefined;\n}","tryCatchPattern":"try {\n  strategy.attach(overlayRef);\n} catch (e) {\n  if (e.message.includes('Scroll strategy has already been attached')) {\n    strategy = overlay.scrollStrategies.close(); // fresh strategy\n    strategy.attach(overlayRef);\n  } else { throw e; }\n}","preventionTips":["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"],"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"}