如何为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
相关产品推荐
相关产品推荐

