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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 09:34:59