Telerik blogs

What developers and end users can agree matters in a data grid: performance! Here you’ll find the ultimate checklist for optimizing your Blazor grid, first framework level and then at the component library level.

Let’s get back to basics and start with a question: As a developer, what would you be looking for in a data grid? Prove me wrong (please do), but I am pretty sure the answer is rich functionality and great performance.

Now, let’s switch roles and ask another question: As an end user, what would you be looking for in a data grid? I am not going to ask you to prove me wrong this time, but something tells me the answer is rich functionality and great performance.

Perhaps ease of customization, export options and out-of-the-box accessibility would come up too, but let’s focus on the common word:

Performance.

Data grids often deal with large datasets. We are all looking for a component that can handle a significant amount of data without compromising performance. Features like pagination, virtualization, lazy loading and smooth scrolling are important for maintaining good performance. After all, a data grid that can efficiently handle and display large datasets without notable slowdown while maintaining responsiveness is priceless.

The purpose of this blog post is to serve as a (hopefully ultimate) guide on being able to set up from the very first time a Telerik Blazor Data Grid with a large set of data and complex interactions and smooth performance. All of this following the Blazor framework best practices. Initially, instead of being blazing fast, it might appear that a decrease in speed originates from the Grid itself—however, this isn’t always accurate, as multiple factors are at play.

First things first, as such is the nature of software development, there is a high probability that your very first-ever Blazor project will require implementing a substantially data-rich Grid (lucky you, right?). That’s why let’s do a blazing fast (no pun intended) tour of what Blazor as technology is and how Telerik UI for Blazor fits into its ecosystem.

The Blazor Framework

Blazor is a web framework developed by Microsoft that allows building of interactive web applications (single-page applications) using C# both on the server side within ASP.NET Core (Blazor Server) or on the client side within the browser through a .NET runtime (Blazor WebAssembly/WASM). Moreover, it’s possible to employ in native mobile and desktop applications using an embedded Web View control (Blazor Hybrid). With .NET 8, a Blazor United model will be introduced as well.

The Blazor Component Library

Progress Telerik UI for Blazor is a collection of 100+ native components for building cross-platform applications. The library supports Blazor WASM, Blazor Server-Side and Blazor Hybrid. And it includes accessible and responsive controls like the Data Grid, Scheduler, Chart and many more, which allow you to minimize the often tedious and time-consuming UI tasks and focus on the business logic of the applications.

A Blazor grid with category beverages shown. Other fields are product name, cost, discontinued, in stock, quantity per unit

The Telerik UI for Blazor Data Grid is built on native Blazor, highly customizable and comprised of hundreds of features like paging, sorting, filtering, editing, grouping, row virtualization, optimized data reading, exporting, globalization and localization, keyboard navigation, accessibility support, etc.

It is my personal favorite component from the entire suite (sorry Scheduler), and it comes with four built-in themes—Default, Bootstrap, Material and Fluent—and with a pure heart I can call it enterprise-ready.

Performance Checklist: Framework Level

Based on the most recent (.NET 7) Microsoft guide, below is a kind of checklist (brace yourselves, it is not a short one) that you can go through first when starting a new project, especially when dealing with rich and complex sets of data. Being completely native to Blazor, the Telerik Blazor Grid immediately benefits from all performance optimization best practices on framework level, too. 

Lazy Loading

Say you’ve chosen the WASM model—the very first thing to stumble upon is the startup load time. This, however, can be improved by waiting to load the needed assemblies until they are required, i.e., lazy loading. Assembly lazy loading doesn’t benefit Blazor Server apps because their assemblies aren’t downloaded to the client.

Follow the framework step-by-step article on project file and router component configurations and how to lazy load assemblies in a hosted WebAssembly solution or just go straight to the full example.

Ahead-of-Time Compilation

AOT is readily available and compiles the application code straight into the native WebAssembly for direct browser execution. AOT-compiled applications lead to increased size and extended download times (which can be mitigated by runtime relinking). However, they often deliver superior runtime performance, particularly when engaging in CPU-intensive operations. For further details, refer to the AOT section here.

Other WASM Optimization Techniques

While on the WebAssembly topic, I am summarizing the tips and tricks specific only to this hosting model optimization like runtime relinking, trimming of unused assemblies and disabling of BlazorEnableTimeZoneSupport into the app project file.

Finally, when deploying, make sure you compress. Inspect the Network tab in a browser’s developer tools and verify that the files are served with Content-Encoding: br (Brotli) or Content-Encoding: gz (Gzip). If the host isn’t serving compressed files, check out this article.

And I almost forgot—whether you build the WebAssembly app in Debug (default) or Release mode makes a difference performance-wise. (To see what your users will see, build your app in Release mode.)

What follows next are tips on minimizing rendering workload in general which, according to Microsoft, could result in x10+ increase in UI rendering speed.

Limit the Rendering of Subtrees

Components form a hierarchy at runtime, with a root component having child components and those children having their own children, and so forth. Often, the entire subtree re-renders. Events affecting higher-level components can lead to costly re-rendering, as every component beneath the high-level one needs to be re-rendered.

You can either utilize primitive immutable types like string, int, bool and DateTime for child component parameters. The built-in mechanism for change detection automatically skips re-rendering if there are no alterations in the primitive immutable parameter values. For instance, when rendering a child component with <Customer CustomerId="@item.CustomerId" /> using an int type for CustomerId, the Customer component remains unaltered unless item.CustomerId changes. Or alternatively, override ShouldRender.

Limit the Component Instances

Say you have a scenario where grids with hundreds of rows are repeated in the UI at scale and rendered at high frequency. Scary right? By fragmenting the UI into distinct components, you enable selective re-rendering of smaller UI segments upon event triggers.

Consider a table with numerous rows, each containing a button. Instead of re-rendering the entire page or table, utilizing a child component might allow only the individual row to re-render. More fragmented components do not always mean more lightweight components, as each still requires memory and CPU.

Avoid having so many components by either inlining child components into their parents or defining reusable RenderFragments in code.

Reduce Parameters Load

If every additional parameter passed to a cell in a grid could add 10 ms to the total rendering, then in a scenario where each cell gets 10 parameters, parameter passing would result in 100 ms time.

Given a cell renders 100 times within the grid, that means a total rendering cost of 10,000 ms (10 s) lag. Multiply that if the components render 1,000 or 10,000 times and the results would be grim.

Microsoft advises bundling multiple parameters in a custom class—a table cell component might accept a common object. For instance, Param A can be different for every cell, but Param B is common for all cell instances.

Cascading Parameters Value

CascadingValue component has an optional IsFixed param which is false by default. Setting IsFixed to true improves performance, provided there are many other components that receive the cascaded value. When the supplied value doesn’t change over time, it is highly recommended to switch it on.

Avoid Attribute Splatting

CaptureUnmatchedValues flag (passing arbitrary additional attributes to the element) can be set to false for components that render at scale, like the cells of a grid. Like this, it would not be necessary to track how multiple copies of the same attribute overwrite each other.

Override SetParametersAsync Method

This one is a bit on the corner-case scenario, but results have shown rendering performance improves up to 25% when you manually implement a parameter-setting logic, i.e., avoid the reflection that the renderer uses. An example on framework level can be found here.

Limit the Events Triggering

Here you would need to use JavaScript interop, but it’s worth as by registering a callback that fires not so often you can avoid endless UI updates on each browser event fire like onmousemove and onscroll. A great example can be found here.

Avoid Re-Render After Invoking Event Handler

Out of the box, components inherit from ComponentBase which invokes StateHasChanged after the their event handlers are invoked. But in a case where an event handler might not modify component state, you can use the IHandleEvent interface to control the behavior of Blazor’s event handling.

In this example, no event handler triggers a re-render, so HandleSelect doesn’t result in a re-render when invoked. You can also prevent re-renders even after a single event handler as shown on the same example.

Avoid Recreating Delegates for Elements in a Loop

A factor to impact performance is the recreation of lambda expression delegates for many repeated elements. Let’s say you have a grid with multiple buttons inside, and each button assigns a delegate to its @onclick event. Rendering speed would be poor.

The example here shows how to render these buttons with a callback for click events via a collection of button objects that assign each button’s @onclick delegate to an Action, and as a result you don’t rebuild all of the button delegates on each button render.

Performance Checklist: Component Library Level

Now that you have checked out everything on the framework side (I warned you to brace yourselves), you easily know what to do to get a blazing fast Telerik Blazor Data Grid. Next we will cover more of the library-specific points to help with the performance of your Telerik Blazor Data Grid.

If you are a fan of educational blogs, a cool way to get started with setting up Telerik Blazor Data Grid is via Jon Hilton’s recent post. If you prefer going through official docs, I’ve got you covered as well—this article describes typical workflow for using the Telerik UI for Blazor component and this one outlines the Telerik Blazor Data Grid. Alternatively, a five-minute learn-the-basics video guide can be found here.

Paging

Despite the plethora of modern monitor models and shapes, fitting more than 20-30 items on the screen will always remain a challenge. Telerik Blazor Data Grid can page the entire data source automatically, or you can hook to an event and fetch each page of data yourself.

The demo below outlines how to enable paging by setting the Pageable param to true, set the initial page to a different one than the first page by using the two-way data binding for the Page parameter. You can also dynamically choose different buttons count, change the items per page, configure the input type, and choose to show or not the page size dropdown.

Play and edit in Telerik REPL.

Blazor grid with multiple page options set: show page sizes - yes, input type - buttons, button count - 5, pager position - bottom

Much more on the basics, events, pager settings and more examples can be found in the paging documentation.

Row Virtualization

Alternative to paging, with row virtualization you can scroll vertically through the records in the data source. The same set of elements is reused to improve performance. While the new data is loading, a loading indicator is shown on the cells.

Play and edit in Telerik REPL or access complete documentation here.

User is scrolling down through data rows

Column Virtualization

Complementary to paging, when column virtualization is enabled, only the DOM elements in the visible viewport of the Grid render, improving the performance and responsiveness.

Play and edit in Telerik REPL.

Grid has been scrolled to the right. The first column is frozen

Note that virtualization is a technique for rendering and UX optimization. It has a few limitations like lack of hierarchy support, grouping (group data on demand is available though), etc. You can check them all here.

Manual Data Operations

An alternative to paging, via manual data operations you can only load and render relevant chunks of data and not all at once. For example, use the OnRead event of the Grid. This demo showcases how to fetch the data required only for the current page depending on its size (by default, the size of the page is 10).

Play and edit in Telerik REPL.

Load on Demand

You can set on-demand loading via a nested component and its OnParemetersSetAsync method as shown in these two projects: load tooltip content on demand and load hierarchy data on demand.

Offload Document Generation

When you want to offload document generation to the server, you can create an HTTP request and gather the processed info gaining from the better server-side performance and it won’t download all the data on the client-side. Check the Export Grid to PDF on the Server sample project.

Real Performance Gains Results

I know that, after all that, you’d like to see real-world examples with actual numbers and measurements. Here you go—this post by Kristian Mariyanov showcases the most recent real-life examples of:

  • Grid with 200 rows and 8 columns: incell editing average time 877.9 ms
  • Grid with 500 rows and 11 columns: render average time 823.8 ms
  • Grid with 100,000 rows and 7 columns: virtualized, render average time 110.1 ms

Happy coding!


About the Author

Petar Grigorov

Petar is a Principal Sales Engineer at Progress with equal sympathy in coding and automation testing. In his spare time he turns into a DIY guy who can successfully setup and use anything from IoT gadgets to paintbrushes and a trowel. When not dreaming of piloting the Millennium Falcon, he is a fan of any engine that is close enough and runs on two or four wheels.

Related Posts

Comments

Comments are disabled in preview mode.