{"record":{"id":"ed5fd099a75120d9","repo":"angular/components","slug":"cdkvirtualscrollviewport-is-already-attached","errorCode":null,"errorMessage":"CdkVirtualScrollViewport is already attached.","messagePattern":"CdkVirtualScrollViewport is already attached\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/cdk/scrolling/virtual-scroll-viewport.ts","lineNumber":288,"sourceCode":"\n  override ngOnDestroy() {\n    this.detach();\n    this._scrollStrategy.detach();\n\n    // Complete all subjects\n    this._renderedRangeSubject.complete();\n    this._detachedSubject.complete();\n    this._viewportChanges.unsubscribe();\n\n    this._isDestroyed = true;\n\n    super.ngOnDestroy();\n  }\n\n  /** Attaches a `CdkVirtualScrollRepeater` to this viewport. */\n  attach(forOf: CdkVirtualScrollRepeater<any>) {\n    if (this._forOf && (typeof ngDevMode === 'undefined' || ngDevMode)) {\n      throw Error('CdkVirtualScrollViewport is already attached.');\n    }\n\n    // Subscribe to the data stream of the CdkVirtualForOf to keep track of when the data length\n    // changes. Run outside the zone to avoid triggering change detection, since we're managing the\n    // change detection loop ourselves.\n    this.ngZone.runOutsideAngular(() => {\n      this._forOf = forOf;\n      this._forOf.dataStream.pipe(takeUntil(this._detachedSubject)).subscribe(data => {\n        const newLength = data.length;\n        if (newLength !== this._dataLength) {\n          this._dataLength = newLength;\n          this._scrollStrategy.onDataLengthChanged();\n        }\n        this._doChangeDetection();\n      });\n    });\n  }\n","sourceCodeStart":270,"sourceCodeEnd":306,"githubUrl":"https://github.com/angular/components/blob/0411926e7d8ae06b32236ec1048a888cfad5abf2/src/cdk/scrolling/virtual-scroll-viewport.ts#L270-L306","documentation":"Each CdkVirtualScrollViewport can host exactly one CdkVirtualScrollRepeater (normally a CdkVirtualForOf). Attaching a second repeater without detaching the first is unsupported, so attach() throws when this._forOf is already set.","triggerScenarios":"Rendering two *cdkVirtualFor directives inside the same cdk-virtual-scroll-viewport; programmatically calling viewport.attach(forOf) twice; using CdkVirtualScrollViewport as the *cdkVirtualFor's view container for another repeater.","commonSituations":"Trying to render headers and rows as two separate virtual-for loops inside one viewport; wrapping the viewport in an *ngIf/*ngFor that duplicates its content; accidentally nesting or duplicating templates that each instantiate a cdkVirtualFor in the same viewport.","solutions":["Use exactly one *cdkVirtualFor per cdk-virtual-scroll-viewport; combine headers/rows into the single loop's template","Move each additional repeating list into its own viewport element","If attaching programmatically, call detach (or destroy the previous repeater) before attaching a new one","Check for duplicated content caused by structural directives wrapping the viewport"],"exampleFix":"<!-- before -->\n<cdk-virtual-scroll-viewport [itemSize]=\"50\">\n  <div *cdkVirtualFor=\"let h of headers\">{{h}}</div>\n  <div *cdkVirtualFor=\"let item of items\">{{item}}</div>\n</cdk-virtual-scroll-viewport>\n<!-- after -->\n<cdk-virtual-scroll-viewport [itemSize]=\"50\">\n  <div *cdkVirtualFor=\"let item of items\">\n    <span *ngIf=\"item.isHeader\" class=\"header\">{{item.label}}</span>\n    <span *ngIf=\"!item.isHeader\">{{item.label}}</span>\n  </div>\n</cdk-virtual-scroll-viewport>","handlingStrategy":"validation","validationCode":"if (viewport['_forOf']) {\n  console.warn('Viewport already has an attached repeater; skipping attach.');\n} else {\n  viewport.attach(forOf);\n}","typeGuard":"function canAttach(viewport: CdkVirtualScrollViewport): boolean {\n  return !(viewport as unknown as { _forOf?: unknown })._forOf;\n}","tryCatchPattern":"try {\n  viewport.attach(forOf);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('already attached')) {\n    viewport.detach();\n    viewport.attach(forOf);\n  } else {\n    throw e;\n  }\n}","preventionTips":["Render exactly one *cdkVirtualFor per cdk-virtual-scroll-viewport","Combine headers/footers into the single loop template instead of extra loops","Avoid structural directives that could duplicate the viewport's content","If attaching programmatically, always detach before re-attaching"],"tags":["angular","cdk","virtual-scroll","duplicate-attach","configuration"],"backgroundTag":"already-attached","analyzedSha":"0411926e7d8ae06b32236ec1048a888cfad5abf2","analyzedAt":"2026-08-31T11:58:23.400Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}