Skip loops, use Virtualization in Blazor

What is Virtualization?

Virtualization is a performance optimization technique for displaying large data sets by rendering only the visible portion within the viewport (the UI area that users can see). For instance, if you have a million records but your browser window can show only 10 at a time, virtualization ensures only those 10 are initially loaded, with additional records dynamically loading as you scroll.

This approach reduces memory usage and improves application responsiveness, making it especially beneficial for long lists. Virtualization is particularly effective for applications that need to display thousands of items, such as data grids, long lists, or complex dashboards.

How Virtualization Works?

In a virtualized UI, only a small subset of currently visible items (with a few buffer items above and below the viewport for smoother scrolling) are rendered on the screen. As the user scrolls, the component dynamically loads and renders new items while removing those that scroll out of view. This allows the UI to maintain consistent performance, regardless of the total number of items.

Lazy Loading: Virtualized components can fetch or load data incrementally as the user scrolls, reducing network load and allowing data to be loaded on demand.

Virtualization in Blazor

In Blazor, virtualization is implemented using the Virtualize component. Before using virtualization, here are some key requirements to keep in mind:

  1. Predictable Item Size Requirement: Virtualization relies on a known ItemSize for smooth operation.
    • If each item is about the same size, Blazor can easily determine which items are visible and load only those.
    • If items vary greatly in size, Blazor may struggle to load the correct items, leading to layout issues or unexpected scrolling behavior.

Example: Without Virtualization

If we render items without virtualization, we might use a foreach loop like the example below.

<ul>
       @foreach(var user in users)
       {
        <li>
           ....
        </li>
       }
</ul>

Code Snippet 1: Component with foreach loop

Here is the full code:

@page "/"
@rendermode InteractiveServer
@inject UserService UserService

<h3>Rendering 10,000 User Profiles using Virtualization</h3>
<p>This will be smooth as browser optimizes the performance.</p>

<ul>
        <Virtualize Items="@users" ItemSize="130" Context="user"> 
        <li style="margin: 10px; padding: 20px; border: 1px solid #ccc;">
            <div style="display: flex; align-items: center;">
                <!-- Conditional styling for FTE employees -->
                <div style="@GetImageBorderStyle(user.IsEmployeeFTE)">
                    <img src="Images/User-Logo.png" alt="User Logo" style="width: 100px; height: 100px;" />
                </div>
                <div style="margin-left: 20px;">
                    <h4>@user.Name</h4>
                    <p style="font-size: 14px;">@user.Description</p>
                    <label>
                        <input type="checkbox" @bind="user.IsEmployeeFTE" />
                        Is FTE
                    </label>
                </div>
            </div>
        </li>
        </Virtualize> 
</ul>

@code {
    private List<User> users;

    protected override void OnInitialized()
    {
        users = UserService.GetUsers(20000);
    }


    private string GetImageBorderStyle(bool isFTE)
    {
        if (isFTE)
        {
            return "border: 5px solid green; padding: 5px;";
        }
        else
        {
            return "border: 5px solid red; padding: 5px;";
        }
    }
}

Code Snippet 2: Full version of code snippet 1

With this approach, all 10,000 items are loaded at once, as you can cross verify in following output. This increases memory usage and significantly impacts performance.

Output 1: Loading list using loops

Example: With Virtualization

To improve performance, we can replace the loop with a Virtualize component as shown below.

<ul>
        <Virtualize Items="@users" ItemSize="130" Context="user"> 
        <li>
            ....
        </li>
        </Virtualize> 
</ul>

Code Snippet 3: Code snippet with virtualization

Now if you notice below, only the 12 items visible in the viewport load initially, and as the user scrolls, only the visible items update instead of loading the entire list. This significantly reduces the load on the UI and provides a smoother experience.

Output 2: Loop with virtualization

Supporting Classes for the Example

Here is the User model used in the example

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public bool IsEmployeeFTE { get; set; }
}

Code Snippet 4: Model Class User

And here is the UserService class:

public class UserService
{
     public List<User> GetUsers(int count)
    {
        var users = new List<User>();
        for (int i = 0; i <= count; i++)
        {
            users.Add(new User
            {
                Id = i,
                Name = $"User {i+1}",
                Description = $"This is a description for User {i+1}.",
                IsEmployeeFTE = i % 2 == 0
            });
        }
        return users;
    }
}

Code Snippet 5: UserService

Ensure to register this service in Program.cs:

builder.Services.AddSingleton<UserService>();

Conclusion

Virtualization in Blazor, implemented via the Virtualize component, allows developers to handle large datasets effectively by only rendering the visible portion of the list. This approach reduces memory usage, improves application performance, and provides a smoother user experience, especially in data-heavy applications. With a predictable item size and efficient data loading, virtualization makes Blazor applications scalable while ensuring responsive UI interactions.


Similar Articles