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

  1. Use exactly one *cdkVirtualFor per cdk-virtual-scroll-viewport; combine headers/rows into the single loop's template
  2. Move each additional repeating list into its own viewport element
  3. If attaching programmatically, call detach (or destroy the previous repeater) before attaching a new one
  4. 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

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


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