Ragnar
Ragnar

Reputation: 181

Angular material CDK tree component with virtual scroll

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

Upvotes: 18

Views: 15926

Answers (2)

Ariel
Ariel

Reputation: 191

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

Upvotes: 19

Benjamin Kindle
Benjamin Kindle

Reputation: 1844

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!

Upvotes: 7

Related Questions