WebAssembly Performance Improvement in Syncfusion Blazor Tree Grid | Syncfusion Blogs
Detailed Blog page Skeleton loader
WASM Performance Improvement in Syncfusion Blazor Tree Grid

The Syncfusion Blazor TreeGrid is a feature-rich component used to visualize self-referential hierarchical (tree-like structure) data effectively in a tabular format. Its features include data binding, editing, sorting, filtering, paging, aggregating rows, and exporting to Excel, CSV, and PDF formats.

In this blog, we are going to discuss the improvements made to the Blazor TreeGrid’s performance in WASM (WebAssembly) applications for the 2020 Volume 3 SP1 release, compared to the previous release version (18.3.0.44). The comparison is fully based on the performance best practice guidelines suggested in the Blazor WebAssembly application documentation.

We received various requests from our customers about improving the TreeGrid’s performance in Blazor WebAssembly applications. So, in response, we took immediate action and improved the performance of the following features in Blazor WebAssembly:

  • Initial Load
  • Filtering
  • Clear Filtering
  • Sorting
  • Paging
  • Selection
  • Column Chooser
  • Column Menu
  • Column Menu Submenu
  • Column Resizing

Even though we improved the Blazor TreeGrid’s performance at the source level, the following Blazor WebAssembly behaviors needed to be avoided on the application side while using Blazor TreeGrid events to improve performance further:

Avoid unnecessary component rendering

Each cell in the TreeGrid component and its child component will be checked for re-rendering by the Blazor diffing algorithm. For instance, the EventCallBack method in the application or TreeGrid will check every child component once the event callback is completed.

The newly provided PreventRender method helps you avoid unnecessary re-rendering of the TreeGrid component. This method internally overrides the ShouldRender method of the TreeGrid to prevent rendering.

In the following example:

  • The PreventRender method is called in the IncrementCount method, which is a click callback.
  • The TreeGrid component will not be a part of the rendering, which happens as a result of the click event, and the currentCount alone will be updated.
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>


<p>Current count: @currentCount</p>


<SfTreeGrid @ref="treeGrid" DataSource="@TreeData" AllowPaging="true" IdMapping="TaskID" ParentIdMapping="ParentID" TreeColumnIndex="1">
    <TreeGridPageSettings PageSize="2"></TreeGridPageSettings>
    <TreeGridColumns>
        <TreeGridColumn Field="TaskID" HeaderText="Task ID" IsPrimaryKey="true" Width="80" TextAlign="Syncfusion.Blazor.Grids.TextAlign.Right"></TreeGridColumn>
        <TreeGridColumn Field="TaskName" HeaderText="Task Name" Width="240"></TreeGridColumn>
        <TreeGridColumn Field="Duration" HeaderText="Duration" Width="100" TextAlign="Syncfusion.Blazor.Grids.TextAlign.Right"></TreeGridColumn>
        <TreeGridColumn Field="Progress" HeaderText="Progress" Width="100"></TreeGridColumn>
        <TreeGridColumn Field="StartDate" HeaderText="StartDate" Width="100"></TreeGridColumn>
    </TreeGridColumns>
</SfTreeGrid>

@code{

    public List<SelfReferenceData> TreeData { get; set; }

    SfTreeGrid<SelfReferenceData> treeGrid { get; set; }

    private int currentCount = 0;

    private void IncrementCount()
    {
        treeGrid.PreventRender();
        currentCount++;
    }

    protected override void OnInitialized()
    {
        TreeData = SelfReferenceData.GetTree();
    }

    public class SelfReferenceData
    {
        public static List<SelfReferenceData> tree = new List<SelfReferenceData>();
        public int? TaskID { get; set; }
        public string TaskName { get; set; }
        public DateTime StartDate { get; set; }
        public DateTime EndDate { get; set; }
        public String Progress { get; set; }
        public String Priority { get; set; }
        public int Duration { get; set; }
        public int? ParentID { get; set; }
        public bool? isParent { get; set; }
        public SelfReferenceData() { }
        public static List<SelfReferenceData> GetTree()
        {
            if (tree.Count == 0)
            {
                int root = -1;
                for (var t = 1; t <= 10; t++)
                {
                    Random ran = new Random();
                    string math = (ran.Next() % 3) == 0 ? "High" : (ran.Next() % 2) == 0 ? "Release Breaker" : "Critical";
                    string progr = (ran.Next() % 3) == 0 ? "Started" : (ran.Next() % 2) == 0 ? "Open" : "In Progress";
                    root++;
                    int rootItem = tree.Count + root + 1;
                    tree.Add(new SelfReferenceData() { TaskID = rootItem, TaskName = "Parent Task " + rootItem.ToString(), StartDate = new DateTime(1992, 06, 07), EndDate = new DateTime(1994, 08, 25), isParent = true, Progress = progr, Priority = math, Duration = ran.Next(1, 50) });
                    int parent = tree.Count;
                    for (var c = 0; c <= 2; c++)
                    {
                        root++;
                        string val = ((parent + c + 1) % 3 == 0) ? "Low" : "Critical";
                        int parn = parent + c + 1;
                        progr = (ran.Next() % 3) == 0 ? "In Progress" : (ran.Next() % 2) == 0 ? "Open" : "Validated";
                        int iD = tree.Count + root + 1;
                        tree.Add(new SelfReferenceData() { TaskID = iD, TaskName = "Child Task " + iD.ToString(), StartDate = new DateTime(1992, 06, 07), EndDate = new DateTime(1994, 08, 25), isParent = (((parent + c + 1) % 3) == 0), ParentID = rootItem, Progress = progr, Priority = val, Duration = ran.Next(1, 50) });
                        if ((((parent + c + 1) % 3) == 0))
                        {
                            int immParent = tree.Count;
                            for (var s = 0; s <= 1; s++)
                            {
                                root++;
                                string Prior = (immParent % 2 == 0) ? "Validated" : "Normal";
                                tree.Add(new SelfReferenceData() { TaskID = tree.Count + root + 1, TaskName = "Sub Task " + (tree.Count + root + 1).ToString(), StartDate = new DateTime(1992, 06, 07), EndDate = new DateTime(1994, 08, 25), isParent = false, ParentID = iD, Progress = (immParent % 2 == 0) ? "On Progress" : "Closed", Priority = Prior, Duration = ran.Next(1, 50) });
                            }
                        }
                    }
                }
            }
            return tree;
        }
    }

}

Notes:

  • The PreventRender method accepts only Boolean arguments that accept true or false values to disable or enable rendering, respectively.
  • The PreventRender method can be used only after the TreeGrid component has completed its initial rendering. Calling this method during initial rendering will not have any effect.

Avoid unnecessary component rendering after TreeGrid events

When a callback method is assigned to the TreeGrid event, the StateHasChanged method will be called in the parent component of the TreeGrid after the completion of the event. This would thereby initiate re-rendering.

You can prevent this re-rendering of the TreeGrid component by setting the PreventRender property of the corresponding event argument to true.

In the following example:

  • The RowSelected event is bound to a callback method. Once the row selection event is completed, the StateHasChanged method will be invoked for the parent component.
  • RowSelectEventArgs<SelfReferenceData>.PreventRender is set to true. Now, the TreeGrid will not be a part of the StateHasChanged method that is invoked as result of the TreeGrid action.
<SfTreeGrid @ref="treeGrid" DataSource="@TreeData" AllowPaging="true" IdMapping="TaskID" ParentIdMapping="ParentID" TreeColumnIndex="2">
    <TreeGridPageSettings PageSize="2"></TreeGridPageSettings>
    <TreeGridSelectionSettings PersistSelection="true"></TreeGridSelectionSettings>
    <TreeGridEvents TValue="SelfReferenceData" RowSelected="OnRowSelected"></TreeGridEvents>
    <TreeGridColumns>
        <TreeGridColumn Type="ColumnType.CheckBox" Width="50"></TreeGridColumn>
        <TreeGridColumn Field="TaskID" HeaderText="Task ID" IsPrimaryKey="true" Width="80" TextAlign="Syncfusion.Blazor.Grids.TextAlign.Right"></TreeGridColumn>
        <TreeGridColumn Field="TaskName" HeaderText="Task Name" Width="240"></TreeGridColumn>
        <TreeGridColumn Field="Duration" HeaderText="Duration" Width="100" TextAlign="Syncfusion.Blazor.Grids.TextAlign.Right"></TreeGridColumn>
        <TreeGridColumn Field="Progress" HeaderText="Progress" Width="100"></TreeGridColumn>
        <TreeGridColumn Field="StartDate" HeaderText="StartDate" Width="100"></TreeGridColumn>
    </TreeGridColumns>
</SfTreeGrid>

@code{

    public List<SelfReferenceData> TreeData { get; set; }

    SelfReferenceData SelectedTask = new SelfReferenceData { };

    private void OnRowSelected(RowSelectEventArgs<SelfReferenceData> args)
    {
        args.PreventRender = true; //without this, you may see noticeable delay in selection with 75 rows in tree grid.
        SelectedTask = args.Data;
    }

    protected override void OnInitialized()
    {
        TreeData = SelfReferenceData.GetTree();
    }


    public class SelfReferenceData
    {
        public static List<SelfReferenceData> tree = new List<SelfReferenceData>();
        public int? TaskID { get; set; }
        public string TaskName { get; set; }
        public DateTime StartDate { get; set; }
        public DateTime EndDate { get; set; }
        public String Progress { get; set; }
        public String Priority { get; set; }
        public int Duration { get; set; }
        public int? ParentID { get; set; }
        public bool? isParent { get; set; }
        public SelfReferenceData() { }
        public static List<SelfReferenceData> GetTree()
        {
            if (tree.Count == 0)
            {
                int root = -1;
                for (var t = 1; t <= 10; t++)
                {
                    Random ran = new Random();
                    string math = (ran.Next() % 3) == 0 ? "High" : (ran.Next() % 2) == 0 ? "Release Breaker" : "Critical";
                    string progr = (ran.Next() % 3) == 0 ? "Started" : (ran.Next() % 2) == 0 ? "Open" : "In Progress";
                    root++;
                    int rootItem = tree.Count + root + 1;
                    tree.Add(new SelfReferenceData() { TaskID = rootItem, TaskName = "Parent Task " + rootItem.ToString(), StartDate = new DateTime(1992, 06, 07), EndDate = new DateTime(1994, 08, 25), isParent = true, Progress = progr, Priority = math, Duration = ran.Next(1, 50) });
                    int parent = tree.Count;
                    for (var c = 0; c <= 2; c++)
                    {
                        root++;
                        string val = ((parent + c + 1) % 3 == 0) ? "Low" : "Critical";
                        int parn = parent + c + 1;
                        progr = (ran.Next() % 3) == 0 ? "In Progress" : (ran.Next() % 2) == 0 ? "Open" : "Validated";
                        int iD = tree.Count + root + 1;
                        tree.Add(new SelfReferenceData() { TaskID = iD, TaskName = "Child Task " + iD.ToString(), StartDate = new DateTime(1992, 06, 07), EndDate = new DateTime(1994, 08, 25), isParent = (((parent + c + 1) % 3) == 0), ParentID = rootItem, Progress = progr, Priority = val, Duration = ran.Next(1, 50) });
                        if ((((parent + c + 1) % 3) == 0))
                        {
                            int immParent = tree.Count;
                            for (var s = 0; s <= 1; s++)
                            {
                                root++;
                                string Prior = (immParent % 2 == 0) ? "Validated" : "Normal";
                                tree.Add(new SelfReferenceData() { TaskID = tree.Count + root + 1, TaskName = "Sub Task " + (tree.Count + root + 1).ToString(), StartDate = new DateTime(1992, 06, 07), EndDate = new DateTime(1994, 08, 25), isParent = false, ParentID = iD, Progress = (immParent % 2 == 0) ? "On Progress" : "Closed", Priority = Prior, Duration = ran.Next(1, 50) });
                            }
                        }
                    }
                }
            }
            return tree;
        }
    }

}

Notes:

  • The PreventRender property internally overrides the ShouldRender method of the TreeGrid to prevent rendering.
  • It is recommended to set PreventRender to true for user interaction events such as RowSelected, RowSelecting, etc., for better performance.
  • For events without any argument, such as DataBound, you can use the PreventRender method to disable rendering.

Performance metrics

The following metrics were taken after rendering 5,000 records with 100 records per page with pagination.

TreeGrid ActionImprovement from Volume 2 to Volume 3 (Using .NET 5)
Initial Load~+73%
Filtering~+74%
Clear Filtering~+80%
Sorting~+85%
Paging~+75%
Selection~+90%
Column Chooser~+69%
Column Menu~+72%
Column Resizing~+88%

The Blazor TreeGrid renders each row and cell as an individual component. Loading large numbers of rows and cells in the view will have a performance impact on both memory consumption and CPU processing.

To use a TreeGrid without such performance impacts, you can load a reduced set of rows in the TreeGrid using paging and virtualization features.

Note: Even with the paging or virtualization feature enabled, having hundreds of rows in a single TreeGrid page will lead to performance lag in an application. So, you need to set a reasonable page size in any scenario.

Conclusion

Thanks for reading! In this blog, we talked about the Blazor TreeGrid’s performance improvement in WebAssembly apps in the 2020 Volume 3 SP1 release. This is done by avoiding rendering unnecessary components. Try it out and leave your feedback in the comments section below.

For existing customers, the newest version of Essential Studio® is available for download from the License and Downloads page. If you are not yet a Syncfusion customer, you can try our 30-day free trial to check out our available features.

You can also contact us through our support forums, Direct-Trac, or feedback portal. We are always happy to assist you!

Be the first to get updates

Maithiliy K

Meet the Author

Maithiliy K

Maithiliy is a Product Manager for web components at Syncfusion. She has 9 years of experience in developing software components and has started her career in Syncfusion in 2010. She is an expert in providing solutions for web products related requirements.