Angular material CDK tree component with virtual scroll
Asked Answered
M

2

18

Angular Material CDK tree component documentation says:

"Flat trees are generally easier to style and inspect. They are also more friendly to scrolling variations, such as infinite or virtual scrolling"

Any ideas how to apply virtual scrolling to CDK flat tree?

I have a massive tree to render and right now it is so slow and when I open all nodes recursively it will crash

I tried < cdk-virtual-scroll-viewport > @angular/cdk-experimental but did not figure it out how to integrate it with tree component

Miss answered 2/10, 2018 at 10:26 Comment(1)
I'm also interested in this topic. Documentation lacks for this... I'm pretty sure that it's simple for expertsRehnberg
S
19

I know this is old, but I came across this thread while trying to figure out the same exact thing, and after much experimentation, I've figured out a basic working example of a virtually scrolling flat tree that requires VERY LITTLE MODIFICATION if you already have a working cdk-tree

The key to my solution was to abandon the cdk-tree directives and use my MatTreeFlatDataSource and FlatTreeControl directly with *cdkVirtualFor. You probably already have these objects set up to pass as inputs into cdk-tree. cdk-tree is actually just a very light wrapper around these two objects that are doing all of the heavy lifting.

Here's a stackblitz, for a more concrete example: https://stackblitz.com/edit/angular-b5nkkd?file=src/app/app.component.html

Here's what it contains:

  • Two scrolling flat trees that draw from the same underlying data source and are controlled by the same underlying tree control (i.e, they hold the same data and whatever you do to one tree will be reflected in the other tree as well)

  • The first tree uses cdk-tree directives, but I couldn't figure out how to get it to work with CDK virtual scroll so it renders all the nodes

  • The second tree does NOT use cdk-tree, but I was able to get virtual scrolling to work cleanly with very little changes. As a result, you have to do styling and some basic logic yourself, but if you look at the difference in template code in the stackblitz, you'll see that it's not so bad.

  • I'm displaying the number of nodes each scroll container is rendering to demonstrate that virtual scrolling is working in one and not the other

Seminary answered 13/11, 2019 at 21:47 Comment(2)
A great answer but be careful if, like me, you have a custom component rendering nodes: <cdk-virtual-scroll-viewport> <my-node *cdkVirtualFor="let node of dataSource" [node]="node"><my-node> </cdk-virtual-scroll-viewport> ...results in cdk-virtual-scroll-content-wrapper having flex-direction: row; giving horizontal scrolling beyond the viewport. Wrapping <my-node> in a <div> fixed it: <cdk-virtual-scroll-viewport> <div *cdkVirtualFor="let node of dataSource"> <my-node [node]="node"><my-node> </div> </cdk-virtual-scroll-viewport>Adabelle
Finnaly, I found solution. It works! Now it's time to implement details from ordinary tree, which has a lot of functionality, so maybe some troubles will appear.Jenellejenesia
C
5

The main function of the virtual viewport is to keep track of scroll events and notify you which elements are currently on screen. Using this information, you can modify the datasource of the tree to only be the nodes that are on screen.

The problem is that right now, the viewport really only works well with items of a consistent height. When you expand a node of the tree, that node has a height that is inconsistent with the rest of the closed ones. To get around this, you may be able to add the child nodes to the virtual viewport's datasource whenever a node is expanded.

For now, I will ignore the expanded node problem.

To get basic virtual scrolling with the tree, add this to your template:

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

We create the viewport, telling it the size of each node. We then add the virtualForOf, passing in the fullDatasource so that the viewport knows how tall it needs to be. This might be cheating a bit because I believe the intended use of virtualForOf is that the template includes the items to be scrolled, but keeping it empty seems to work.

The only thing left is to make sure the tree's datasource is only the visible items of the full datasource. We'll change how we declare it initially in the constructor, but this is the more exciting part:

  ngAfterViewInit() {
    this.virtualScroll.renderedRangeStream.subscribe(range => {
      console.log(range, 'range')
      this.dataSource.data = this.fullDatasource.slice(range.start, range.end)
    })
  }

We subscribe to the renderedRangeStream which emits a range whenever the scroll changes. Whenever that happens, we simply set the datasource equal to the appropriate slice!

Stackblitz with result Hopefully this is enough to get you started!

Churrigueresque answered 6/1, 2019 at 12:38 Comment(3)
could you look into it #56064733Okra
A slight problem with this approach: renderedRangeStream runs outside the Angular zone, so you may have problems with change detection on the rendered tree nodes. You can solve this by injecting CdkVirtualForOf as the view-child, and subscribing to its viewChange observable instead.Michelinemichell
One problem i see with this approach is that a mat-tree can be recursive, while this appoach would just slice down the length of the root elements. if there are many items in the second level, this approach would not help with scrolling at all.Keith

© 2022 - 2024 — McMap. All rights reserved.