使用ViewChild获取ng-template内CdkVirtualScrollViewport报错求助
问题解决:Angular CDK Overlay内CdkVirtualScrollViewport无法通过ViewChild获取实例
问题根源
CdkVirtualScrollViewport位于cdkConnectedOverlay的ng-template中,只有当Overlay打开(isMenuOpen为true)时,该模板才会被渲染为实际DOM节点。而ngAfterViewInit在组件初始化完成后立即执行,此时Overlay大概率还未打开,因此@ViewChild无法找到对应实例,导致后续调用elementScrolled()时抛出undefined错误。
解决方案
通过监听Overlay的挂载事件,在视图实际渲染完成后再获取虚拟滚动实例并设置滚动监听,同时处理内存泄漏问题。
步骤1:修改模板代码
给虚拟滚动视图添加模板引用变量,并绑定Overlay的attach和detach事件:
<ng-template cdkConnectedOverlay cdkConnectedOverlayBackdropClass="cdk-overlay-transparent-backdrop" [cdkConnectedOverlayHasBackdrop]="true" [cdkConnectedOverlayOrigin]="trigger" [cdkConnectedOverlayOpen]="isMenuOpen" (detach)="onOverlayDetached()" (backdropClick)="isMenuOpen = false" (attach)="onOverlayAttached()"> <!-- 添加模板引用变量#virtualScroll --> <cdk-virtual-scroll-viewport itemSize="50" #virtualScroll> <mat-option style="height: 50px;" (click)="optionClicked($event, data)" *cdkVirtualFor="let data of filteredData;"> <div (click)="optionClicked($event, data)"> <mat-checkbox [checked]="data.selected"> {{ data.skillName }} </mat-checkbox> </div> </mat-option> </cdk-virtual-scroll-viewport> </ng-template>
步骤2:修改组件逻辑
通过模板引用变量获取实例,在Overlay挂载后设置滚动监听,卸载时取消订阅避免内存泄漏:
import { Component, ViewChild, AfterViewInit, ChangeDetectorRef, Subscription } from '@angular/core'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; @Component({ // 组件元数据 }) export class MultiselectAutocompleteComponent implements AfterViewInit { // 通过模板引用变量获取实例 @ViewChild('virtualScroll') cdkVirtualScrollViewport: CdkVirtualScrollViewport; private scrollSubscription: Subscription; isLoading = false; filteredData = []; constructor(private cdr: ChangeDetectorRef) {} ngAfterViewInit() { // 此处不直接初始化监听,等待Overlay挂载 } // Overlay挂载完成后触发 onOverlayAttached() { // 强制触发变更检测,确保视图已更新 this.cdr.detectChanges(); if (this.cdkVirtualScrollViewport) { this.setupScrollListener(); } } // 设置滚动触底监听 setupScrollListener() { this.scrollSubscription = this.cdkVirtualScrollViewport.elementScrolled().pipe( map(() => this.cdkVirtualScrollViewport.measureScrollOffset('bottom')), pairwise(), filter(([y1, y2]) => (y2 < y1) && (y2 < 70)), tap(() => this.isLoading = true), switchMap((_) => { /* 替换为你的API调用逻辑 */ }) ).subscribe(data => { // 处理API返回数据,追加到现有列表 this.filteredData = [...this.filteredData, ...data]; this.isLoading = false; }); } // Overlay卸载时取消订阅 onOverlayDetached() { this.isMenuOpen = false; if (this.scrollSubscription) { this.scrollSubscription.unsubscribe(); this.scrollSubscription = null; } } // 其他组件方法... optionClicked(event: Event, data: any) { // 你的点击逻辑 } }
关键注意事项
- 使用模板引用变量(
#virtualScroll)可以更精准地定位目标实例,避免多个同类型组件冲突。 - 调用
ChangeDetectorRef.detectChanges()确保Overlay挂载后视图已完成更新,保证@ViewChild能获取到实例。 - 必须在Overlay卸载时取消滚动事件订阅,防止内存泄漏。
内容的提问来源于stack exchange,提问作者Sefat Anam
相关产品推荐
相关产品推荐

