Optimizing Blazor TreeView Performance with Virtualization

Jollen Moyani - Jul 17 - - Dev Community

TL;DR: Enhance the performance of the Syncfusion Blazor TreeView component with the new virtualization feature added in the 2024 volume 2 release. Learn how to implement and benefit from this feature!

Syncfusion Blazor TreeView is a UI component that displays hierarchical data such as a table of contents, code examples, and file directories in a tree-like structure. It is a feature-rich component, supporting data binding, load-on-demand, multiple selection, drag and drop, node editing, checkboxes, templates, and more in both Blazor WebAssembly (WASM) and Blazor Server apps.

The load-on-demand (lazy-load) feature is enabled by default to reduce bandwidth usage when consuming large amounts of data. Only first-level nodes are loaded initially, and then child nodes are loaded when their parent node is expanded.

If a parent node has multiple sub-levels of child nodes, the load-on-demand feature will load only the child nodes of the corresponding parent when expanded. In this scenario, the child nodes of collapsed parent nodes will not be loaded.

However, in cases where a large number of parent nodes are rendered, or a single parent node with many child nodes is rendered, a more efficient solution than the load-on-demand feature is needed. This is where virtualization becomes essential.

Virtualization

Virtualization involves rendering nodes for the current viewport alone and avoiding rendering off-screen items. This technique ensures optimal performance when dealing with huge datasets.

The Blazor TreeView’s UI virtualization dynamically loads the nodes when the user scrolls the container scroller. The virtualization process is intelligently managed based on the Height property of the TreeView component. This support has been included in the latest Essential Studio 2024 Volume 2 release.

This blog will examine virtualization’s benefits and how to implement it in the Blazor TreeView component.

Benefits of virtualization

Let’s explore the benefits that virtualization brings to the Blazor TreeView component!

Addressing performance challenges

Dealing with a multitude of DOM elements often leads to performance challenges. Browser engines may struggle to manage large DOM structures effectively, even when a component optimizes data. The challenge lies in ensuring seamless webpage performance while interacting with many DOM elements.

In Blazor TreeView, the UI virtualization feature comes into play when you need to display thousands of nodes in a single TreeView. Initially, the component retrieves the entire data source for the component and renders only the number of nodes that adapt to the current viewport. The rest of the nodes will be loaded on demand through scrolling. Keyboard scrolling is supported as well.

Reducing initial loading time

Virtualization also helps us reduce the time taken for the initial rendering of the Blazor TreeView component bound to a large data set, which will speed up the app.

Steps to enable virtualization in Blazor TreeView

Follow these steps to enable the virtualization feature in the Blazor TreView component:

1.First, render the TreeView component in your Blazor app with the required data source, as per the getting started documentation.
2.Refer to this documentation, to know the data binding types supported in the Blazor TreeView.
3.Finally, enable the virtualization feature in the Blazor TreeView by setting the EnableVirtualization property to true. Refer to the following code example.

@using Syncfusion.Blazor.Navigations
@using Syncfusion.Blazor.Data

<div class="control_wrapper">
 @*Initialize the TreeView component*@
  <SfTreeView TValue="NodeResult" EnableVirtualization=true Height="400px">
    <TreeViewFieldsSettings TValue="NodeResult" Id="Id" Text="Name" ParentID="Pid" HasChildren="HasChild" Expanded="Expanded" Query="TreeViewQuery TreeViewQuery ">
      <SfDataManager Url="api/Nodes" CrossDomain="true" Adaptor="Syncfusion.Blazor.Adaptors.WebApiAdaptor"></SfDataManager>
    </TreeViewFieldsSettings>
    <TreeViewTemplates TValue="NodeResult">
      <NodeTemplate>
        <div>@context.Name</div>
      </NodeTemplate>
    </TreeViewTemplates>
  </SfTreeView>
</div>

@code {
  public Query TreeViewQuery = new Query();
   public class NodeResult
   {
      public int? Id { get; set; }
      public string Name { get; set; }
      public int? Pid { get; set; }
      public bool HasChild { get; set; }
      public bool Expanded { get; set; }
      public bool Checked { get; set; }
      public bool Selected { get; set; }
   }

}
Enter fullscreen mode Exit fullscreen mode

Refer to the code for the Web API controller.

using Microsoft.AspNetCore.Mvc;

namespace TreeView.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class NodesController : ControllerBase
    {
        [HttpGet]
        public IEnumerable<NodeResult> Get()
        {
            var queryString = Request.Query;
            if (queryString.Keys.Contains("$filter"))
            {
                // Load the child data based on the expanded parent.
                string filter = string.Join("", queryString["$filter"].ToString().Split(' ').Skip(2)); // Get filter from querystring.
                int parentId = int.Parse(filter);
                List<NodeResult> TreeViewData = GetItemsFromId(parentId);
                return TreeViewData;
            }
            else
            {
                // Get first-level tree data alone during the initial rendering.
                return GetRootData();
            }
        }

        private static List<NodeResult> GetRootData()
        {
            return new List<NodeResult>()
            {
                new NodeResult() { Id = 1, Name = "Software Developers", HasChild = true, Expanded = true,Checked = false, Selected= true },
                new NodeResult() { Id = 2, Name = "UX/UI Designers", HasChild = true, Expanded = false,Checked = false, Selected= true },
                new NodeResult() { Id = 3, Name = "Quality Testers", HasChild = true, Expanded = false,Checked = false, Selected= true },
                new NodeResult() { Id = 4, Name = "Technical Support", HasChild = true, Expanded = false,Checked = false, Selected= true },
                new NodeResult() { Id = 5, Name = "Network Engineers", HasChild = true, Expanded = false,Checked = false, Selected= true }
            };
        }

        private List<NodeResult> GetItemsFromId(int id)
        {
            List<NodeResult> DefaultData = new List<NodeResult>()
            {
                new NodeResult() { Name = "Nancy" },
                new NodeResult() { Name = "Andrew" },
                new NodeResult() { Name = "Janet" },
                new NodeResult() { Name = "Margaret" },
                new NodeResult() { Name = "Steven" },
                new NodeResult() { Name = "Laura" },
                new NodeResult() { Name = "Robert" },
                new NodeResult() { Name = "Michael" },
                new NodeResult() { Name = "Albert" },
                new NodeResult() { Name = "Nolan" },
                new NodeResult() { Name = "Jennifer" },
                new NodeResult() { Name = "Carter" },
                new NodeResult() { Name = "Allison" },
                new NodeResult() { Name = "John" },
                new NodeResult() { Name = "Susan" },
                new NodeResult() { Name = "Lydia" },
                new NodeResult() { Name = "Kelsey" },
                new NodeResult() { Name = "Jessica" },
                new NodeResult() { Name = "Shelley" },
                new NodeResult() { Name = "Jack" }
            };

            int count = 10000 * id;
            List<NodeResult> TreeViewData = Enumerable.Range(0, 2000)
            .Select(j =>
            {
                count++;
                return new NodeResult { Id = count, Name = DefaultData[j % DefaultData.Count].Name + " - " + count.ToString(), Pid = id, Checked = false, Selected = true };
            }).ToList();
            return TreeViewData;
        }

        public class NodeResult
        {
            public int? Id { get; set; }
            public string Name { get; set; }
            public int? Pid { get; set; }
            public bool HasChild { get; set; }
            public bool Expanded { get; set; }
            public bool Checked { get; set; }
            public bool Selected { get; set; }
        }

    }
 }
Enter fullscreen mode Exit fullscreen mode

Refer to the following GIF image demonstrating the seamless loading of nodes in the Blazor TreeView component.

Implementing virtualization in the Blazor TreeView component

Implementing virtualization in the Blazor TreeView component

Performance metrics

Let’s see the performance difference between the TreeView rendered with and without virtualization on the Blazor server and WebAssembly (WASM) apps. This is the status as per the 2024 Volume 2 release.

Nodes count

Server without virtualization

Server with virtualization

WASM without virtualization

WASM with virtualization

10 k data

40730 ms 

920 ms 

120000 ms 

7750 ms 

10 K data (All nodes are selected and rendered using templates)

90000 ms

950 ms 

138000 ms 

8400 ms 

The above table proves that the virtualization feature considerably enhances the performance of the Blazor TreeView.

References

For more details, refer to the Virtualization in Blazor TreeView demo and documentation.

Conclusion

Thanks for reading! This article provides a clear and straightforward guide for implementing virtualization in the Blazor TreeView component. We hope you found it helpful. If you have any questions, feel free to share them in the comments section below.

You can also check out all the other features rolled out in the 2024 volume 2 release on our Release Notes and What’s New pages.

Try out these features and share your feedback as comments on this blog. You can also reach us through our support forums, support portal, or feedback portal.

Related blogs

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .