You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为cdk-virtual-scroll-viewport设置列表总项数

解决cdk-virtual-scroll-viewport动态指定总项数&加载更多问题

核心原因

默认使用普通数组或BehaviorSubject作为虚拟滚动数据源时,CDK会默认以当前已加载数组的长度作为总项数计算滚动范围,大部分基础教程未提及*cdkVirtualFor指令内置的totalLength配置项,才会出现需要提前初始化大长度数组的不合理方案。

最佳实现方案

步骤1:自定义动态数据源(可选,普通Observable流也可适配)

继承CDK提供的DataSource抽象类,维护已加载数据和总项数两个独立状态,避免将总项数和已加载数据长度绑定:

import { DataSource, CollectionViewer } from '@angular/cdk/collections';
import { BehaviorSubject, Observable } from 'rxjs';

export class DynamicVirtualScrollDataSource<T> extends DataSource<T> {
  // 存储已加载的实际数据
  private loadedItems$ = new BehaviorSubject<T[]>([]);
  // 总项数,可动态修改,不和已加载数据长度绑定
  public totalCount = 0;

  connect(collectionViewer: CollectionViewer): Observable<T[]> {
    return this.loadedItems$.asObservable();
  }

  disconnect(collectionViewer: CollectionViewer): void {
    this.loadedItems$.complete();
  }

  // 追加分页加载的新数据
  appendItems(items: T[]) {
    const currentItems = this.loadedItems$.value;
    this.loadedItems$.next([...currentItems, ...items]);
  }

  // 手动更新总项数,一般从后端分页接口返回的总条数获取
  setTotalCount(count: number) {
    this.totalCount = count;
  }
}

步骤2:模板中配置totalLength参数

totalLength是*cdkVirtualFor的内置输入属性,直接绑定之前维护的总项数即可,不需要提前构造空数组占位:

<cdk-virtual-scroll-viewport itemSize="50" style="height: 600px; width: 100%;">
  <div 
    *cdkVirtualFor="let item of dataSource; totalLength: dataSource.totalCount; let i = index"
    style="height: 50px; padding: 0 16px; line-height: 50px;"
  >
    第{{i}}项:{{item.content}}
  </div>
</cdk-virtual-scroll-viewport>

步骤3:实现滚动加载更多逻辑

监听虚拟滚动视口的滚动事件,接近已加载数据底部时触发分页请求,更新数据和总项数即可:

import { Component, ViewChild } from '@angular/core';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';

// 你的列表项类型定义
interface ListItem {
  id: number;
  content: string;
}

@Component({
  // 组件配置
})
export class YourListComponent {
  dataSource = new DynamicVirtualScrollDataSource<ListItem>();
  @ViewChild(CdkVirtualScrollViewport) viewport!: CdkVirtualScrollViewport;
  private currentPage = 1;
  private pageSize = 10;

  ngOnInit() {
    // 首次加载数据
    this.loadPageData();

    // 监听滚动事件触发加载更多
    this.viewport.elementScrolled().subscribe(() => {
      const renderedEndIndex = this.viewport.getRenderedRange().end;
      const loadedTotal = this.dataSource['loadedItems$'].value.length;
      // 渲染到已加载数据最后2条时触发下一页加载,避免空白
      if (renderedEndIndex >= loadedTotal - 2 && loadedTotal < this.dataSource.totalCount) {
        this.loadPageData();
      }
    });
  }

  private loadPageData() {
    // 调用你的后端分页接口
    yourListApi(this.currentPage, this.pageSize).subscribe(res => {
      if (this.currentPage === 1) {
        // 首次请求时设置总项数
        this.dataSource.setTotalCount(res.total);
      }
      // 追加新加载的数据
      this.dataSource.appendItems(res.list);
      this.currentPage++;
    });
  }
}

适配无限滚动场景(未知总项数)

如果是无总条数的无限滚动场景,不需要调用setTotalCount,每次加载完新数据后手动将totalCount设置为已加载条数 + 1,即可一直预留滚动空间,触发下一次加载后再更新totalCount即可。


内容的提问来源于stack exchange,提问作者Trevortni

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 05:39:03