如何结合虚拟滚动懒加载Angular独立组件?
Angular独立组件与CDK虚拟滚动懒加载实现示例
核心实现思路
结合CDK虚拟滚动的视口可见范围监听,动态导入独立组件(无需NgModule声明),通过ViewContainerRef在元素进入视口时创建组件实例,移出视口时销毁实例以节省资源。
完整代码示例
1. 定义独立SVG组件
所有懒加载组件需标记standalone: true,以圆形SVG组件为例:
// circle-svg.component.ts import { Component, Input } from '@angular/core'; @Component({ selector: 'app-circle-svg', standalone: true, template: ` <svg width="50" height="50"> <circle cx="25" cy="25" r="20" fill="{{ fillColor }}" /> </svg> `, }) export class CircleSvgComponent { @Input() fillColor = 'blue'; }
同理可创建square-svg.component.ts、triangle-svg.component.ts等其他SVG组件,结构与上述一致。
2. 主虚拟滚动组件
实现视口监听、动态加载/销毁组件逻辑:
// virtual-scroll-lazy.component.ts import { Component, ViewChild, ViewChildren, ViewContainerRef, QueryList, OnInit, OnDestroy } from '@angular/core'; import { CdkVirtualScrollViewport, ScrollingModule } from '@angular/cdk/scrolling'; import { CommonModule } from '@angular/common'; @Component({ selector: 'app-virtual-scroll-lazy', standalone: true, imports: [CommonModule, ScrollingModule], template: ` <cdk-virtual-scroll-viewport itemSize="50" class="viewport"> <div *cdkVirtualFor="let compName of componentNames; trackBy: trackByIndex" class="item" #itemContainer ></div> </cdk-virtual-scroll-viewport> `, styles: [` .viewport { height: 400px; width: 200px; border: 1px solid #eee; } .item { height: 50px; display: flex; align-items: center; justify-content: center; } `] }) export class VirtualScrollLazyComponent implements OnInit, OnDestroy { @ViewChild(CdkVirtualScrollViewport) viewport!: CdkVirtualScrollViewport; @ViewChildren('itemContainer', { read: ViewContainerRef }) itemContainers!: QueryList<ViewContainerRef>; // 存储组件实例引用,用于销毁 private componentRefs = new Map<number, any>(); // 组件名称列表(对应业务中的字符串列表) componentNames = ['circle-svg', 'square-svg', 'triangle-svg']; ngOnInit() { // 监听滚动事件,更新可见组件 this.viewport.elementScrolled().subscribe(() => this.updateVisibleComponents()); // 初始加载可见区域组件 this.updateVisibleComponents(); } ngOnDestroy() { // 销毁所有动态创建的组件,避免内存泄漏 this.componentRefs.forEach(ref => ref.destroy()); this.componentRefs.clear(); } trackByIndex(index: number) { return index; } private async updateVisibleComponents() { const { start, end } = this.viewport.getRenderedRange(); // 销毁超出视口范围的组件 this.componentRefs.forEach((ref, index) => { if (index < start || index >= end) { ref.destroy(); this.componentRefs.delete(index); } }); // 加载当前视口内未初始化的组件 for (let i = start; i < end; i++) { if (!this.componentRefs.has(i)) { const compName = this.componentNames[i % this.componentNames.length]; // 动态导入独立组件 const module = await import(`./${compName}.component`); // 转换组件名称为大驼峰格式(匹配导出类名) const compClassName = compName.split('-') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join('') + 'Component'; const compClass = module[compClassName]; const container = this.itemContainers.get(i); if (container) { // 创建组件实例 const compRef = container.createComponent(compClass); // 传递输入属性(示例:随机颜色) compRef.instance.fillColor = this.getRandomColor(); this.componentRefs.set(i, compRef); } } } } private getRandomColor() { const colors = ['red', 'green', 'blue', 'orange', 'purple']; return colors[Math.floor(Math.random() * colors.length)]; } }
关键注意事项
- 独立组件配置:所有懒加载组件必须设置
standalone: true,无需添加到任何NgModule的declarations数组。 - 路径匹配:动态导入的路径需与组件文件实际位置一致,确保编译时能正确解析。
- 内存管理:必须在组件销毁时清理所有动态创建的组件实例,避免内存泄漏。
- 性能优化:使用
trackBy函数减少虚拟滚动时的DOM重绘,提升滚动流畅度。 - 输入输出处理:通过
componentRef.instance可直接设置组件输入属性或监听输出事件,满足业务交互需求。
内容的提问来源于stack exchange,提问作者Benjamin McFerren
相关产品推荐
相关产品推荐

