带有虚拟滚动的Angular Material CDK树组件

16

Angular Material CDK tree组件文档中提到:

"扁平树通常更容易进行样式和检查。 它们也更适合滚动变化,例如无限卷动或 虚拟卷动。"

有什么方法可以将虚拟滚动应用于CDK扁平树?

我有一个需要渲染的大型树,目前速度非常慢,当我递归地打开所有节点时,它会崩溃。

我尝试使用 < cdk-virtual-scroll-viewport > @angular/cdk-experimental,但没有想出如何将其与树组件集成。


1
我也对这个话题很感兴趣。但是缺乏相关文档... 我相信对于专家来说这很简单。 - Alexander Kondaurov
2个回答

18

我知道这是过时的,但在尝试解决完全相同的问题时,我遇到了这个线程,并经过大量实验,我找到了一个基本工作示例,可以通过几乎滚动的平面树来实现 VERY LITTLE MODIFICATION(如果您已经有一个工作的 cdk-tree)。

我的解决方案的关键是放弃 cdk-tree 指令并直接使用我的 MatTreeFlatDataSource 和 FlatTreeControl 与 *cdkVirtualFor 直接使用。您可能已经设置好这些对象以将其作为输入传递给 cdk-tree。 cdk-tree 实际上只是围绕这两个对象的一个非常轻巧的包装器,这两个对象正在执行所有重活。

以下是 stackblitz,以获得更具体的示例: https://stackblitz.com/edit/angular-b5nkkd?file=src/app/app.component.html

它包含以下内容:

  • 两个滚动平面树,从同一底层数据源绘制,并由相同的底层树控件控制(即,它们保存相同的数据,您对一个树所做的任何操作也将反映在另一个树中)

  • 第一个树使用 cdk-tree 指令,但我无法弄清楚如何让其与 CDK 虚拟滚动一起工作,因此它会呈现所有节点

  • 第二棵树没有使用cdk-tree,但我成功地只做了很少的更改就使虚拟滚动起作用。因此,您需要自己进行样式和一些基本逻辑的处理,但是如果您比较一下stackblitz中的模板代码差异,您会发现它并不那么糟糕。

  • 我正在显示每个滚动容器呈现的节点数以证明虚拟滚动在其中一个上起作用而另一个上则没有。


1
一个很好的答案,但是如果像我一样有自定义组件渲染节点的话要小心:<cdk-virtual-scroll-viewport> <my-node *cdkVirtualFor="let node of dataSource" [node]="node"><my-node> </cdk-virtual-scroll-viewport>...会导致 cdk-virtual-scroll-content-wrapper 具有 flex-direction: row;,从而在视口之外产生水平滚动。将 <my-node> 包装在 <div> 中可以解决这个问题:<cdk-virtual-scroll-viewport> <div *cdkVirtualFor="let node of dataSource"> <my-node [node]="node"><my-node> </div> </cdk-virtual-scroll-viewport> - Jack
1
终于,我找到了解决方案。它有效!现在是时候从普通树中实现细节了,它有很多功能,所以可能会出现一些问题。 - greygreg87

5
虚拟视口的主要功能是跟踪滚动事件并通知您当前屏幕上显示的元素。利用这些信息,您可以修改树的数据源,使其只包含当前在屏幕上的节点。但问题是,目前视口只适用于高度一致的项目。当您展开树的某个节点时,该节点的高度与其他关闭的节点不一致。为了解决这个问题,您可能需要在展开节点时将子节点添加到虚拟视口的数据源中。暂时忽略扩展节点的问题。要使用基本的虚拟滚动树,请将此内容添加到您的模板中:
<cdk-virtual-scroll-viewport itemSize="48" style="height: 200px;">
  <ng-container *cdkVirtualFor="let item of fullDatasource"></ng-container>

  <mat-tree [dataSource]="dataSource" [treeControl]="treeControl">...</mat-tree-node>
  </mat-tree>
</cdk-virtual-scroll-viewport>

我们创建视口(viewport),告诉它每个节点的大小。然后添加 virtualForOf,将 fullDatasource 传递给它,以便视口知道需要多高。这可能有点作弊,因为我认为 virtualForOf 的预期用途是模板包含要滚动的项目,但保持为空似乎也可以工作。
唯一剩下的事情就是确保树的数据源只包含完整数据源中可见的项目。我们将更改在构造函数中最初声明它的方式,但这是更令人兴奋的部分:
  ngAfterViewInit() {
    this.virtualScroll.renderedRangeStream.subscribe(range => {
      console.log(range, 'range')
      this.dataSource.data = this.fullDatasource.slice(range.start, range.end)
    })
  }

我们订阅了renderedRangeStream,它在滚动更改时会发射一个范围。每当这种情况发生时,我们只需将数据源设置为适当的切片即可!请参考Stackblitz示例,希望能够帮助您入门!

你能看一下这个吗?https://stackoverflow.com/questions/56064233/how-to-implement-virtual-scroll-on-material-tree-grand-child-level - androidGenX
1
这种方法存在一个小问题:renderedRangeStream 在 Angular 区域之外运行,因此您可能会在渲染的树节点上遇到变更检测问题。您可以通过将 CdkVirtualForOf 注入为视图子组件,并订阅其 viewChange 可观察对象来解决此问题。 - Jon Rimmer
3
我看到这种方法的一个问题是mat-tree可能是递归的,而这个方法只会切掉根元素的长度。如果第二层有许多项目,则此方法对滚动没有任何帮助。 - Mr.Manhattan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接