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:
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.
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.
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.
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.
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.
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.
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.
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
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.
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
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
Customer component remains unaltered unless
item.CustomerId changes. Or alternatively,
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.
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.
CascadingValue component has an optional
IsFixed param which is false by default. Setting
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.
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.
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.
A great example can be found here.
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,
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.
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.
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.
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
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.
Much more on the basics, events, pager settings and more examples can be found in the paging documentation.
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.
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.
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.
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).
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.
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.
Subscribe to be the first to get our expert-written articles and tutorials for developers!
All fields are required