angular/components · error
CdkVirtualScrollViewport is already attached.
Error message
CdkVirtualScrollViewport is already attached.
What it means
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.
Source
Thrown at src/cdk/scrolling/virtual-scroll-viewport.ts:288
override ngOnDestroy() {
this.detach();
this._scrollStrategy.detach();
// Complete all subjects
this._renderedRangeSubject.complete();
this._detachedSubject.complete();
this._viewportChanges.unsubscribe();
this._isDestroyed = true;
super.ngOnDestroy();
}
/** Attaches a `CdkVirtualScrollRepeater` to this viewport. */
attach(forOf: CdkVirtualScrollRepeater<any>) {
if (this._forOf && (typeof ngDevMode === 'undefined' || ngDevMode)) {
throw Error('CdkVirtualScrollViewport is already attached.');
}
// Subscribe to the data stream of the CdkVirtualForOf to keep track of when the data length
// changes. Run outside the zone to avoid triggering change detection, since we're managing the
// change detection loop ourselves.
this.ngZone.runOutsideAngular(() => {
this._forOf = forOf;
this._forOf.dataStream.pipe(takeUntil(this._detachedSubject)).subscribe(data => {
const newLength = data.length;
if (newLength !== this._dataLength) {
this._dataLength = newLength;
this._scrollStrategy.onDataLengthChanged();
}
this._doChangeDetection();
});
});
}
View on GitHub (pinned to 0411926e7d)
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
Example fix
<!-- before -->
<cdk-virtual-scroll-viewport [itemSize]="50">
<div *cdkVirtualFor="let h of headers">{{h}}</div>
<div *cdkVirtualFor="let item of items">{{item}}</div>
</cdk-virtual-scroll-viewport>
<!-- after -->
<cdk-virtual-scroll-viewport [itemSize]="50">
<div *cdkVirtualFor="let item of items">
<span *ngIf="item.isHeader" class="header">{{item.label}}</span>
<span *ngIf="!item.isHeader">{{item.label}}</span>
</div>
</cdk-virtual-scroll-viewport> Defensive patterns
Strategy: validation
Validate before calling
if (viewport['_forOf']) {
console.warn('Viewport already has an attached repeater; skipping attach.');
} else {
viewport.attach(forOf);
} Type guard
function canAttach(viewport: CdkVirtualScrollViewport): boolean {
return !(viewport as unknown as { _forOf?: unknown })._forOf;
} Try / catch
try {
viewport.attach(forOf);
} catch (e) {
if (e instanceof Error && e.message.includes('already attached')) {
viewport.detach();
viewport.attach(forOf);
} else {
throw e;
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- CDK virtual scroll: maxBufferPx must be greater than or equa
- Error: cdk-virtual-scroll-viewport requires the "itemSize" p
- cdk-virtual-scroll: scrolledIndexChange is currently not sup
- cdk-virtual-scroll: scrollToIndex is currently not supported
- CDK virtual scroll: maxBufferPx must be greater than or equa
AI-assisted analysis of angular/components@0411926e7d (2026-08-31).
Data as JSON: /api/errors/ed5fd099a75120d9.
Report an issue: GitHub.