Reusable loading Bar Component in Blazor

Sometimes we have a task which takes a bit longer. Just imagine you want to grab a big amount of data, process this data and then make it visible to the user. If this takes a bit of time nothing really happens from the eyes of the user. So lets create a reusable loading bar component in Blazor.

The loading bar component

We will keep it simple for the beginning. Feel free to tweak everything you want. As Blazor comes with Bootstrap, we will use Bootstrap for our loading bar itself. So let's create a new Loading razor component:

<!-- This is our loading component -->
<div>
    <p>Some description here</p>

    <div class="progress">
        <div class="progress-bar progress-bar-striped" role="progressbar"
             style="width: 0%"
             aria-valuenow="0"
             aria-valuemin="0"
             aria-valuemax="0"></div>
    </div>
</div>


<div>
    <!-- This is our real component which we will show once the loading is done -->
</div>
}

We need a few things here to do:

  1. We have to switch the visibility of our loading bar component and of our "real" rendered component. As said we will keep it very simple. You can adjust this to your needs.
  2. We don't want to know the "real" component from the eyes of the LoadingComponent otherwise it wouldn't be really reusable.
  3. The "real" component has to set the title, total steps and the current step from within its component.

The first step is we need a container which models some of the requirements. This container is hosted by the LoadingComponent and gets passed down to the real component. The LoadingComponent also has to listen to changes when the container changes. For all of you which worked with WPF or UWP you will feel "at home". We will use the INotifyPropertyChanged interface.

This is our model:

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace BlazorLoadingComponent.Shared;

public class LoadingConfiguration : INotifyPropertyChanged
{
    private string _title;
    private int _currentStep;
    private int _totalSteps;
    private bool _isLoading;

    public string Title
    {
        get => _title;
        set
        {
            _title = value;
            OnPropertyChanged();
        }
    }

    public int CurrentStep
    {
        get => _currentStep;
        set
        {
            _currentStep = value;
            OnPropertyChanged();
        }
    }

    public int TotalSteps
    {
        get => _totalSteps;
        set
        {
            _totalSteps = value;
            OnPropertyChanged();
        }
    }

    public bool IsLoading
    {
        get => _isLoading;
        set
        {
            _isLoading = value;
            OnPropertyChanged();
        }
    }

    public int GetPercentage()
    {
        return TotalSteps > 0 ? (int)((double)CurrentStep / (double)TotalSteps * 100) : 0;
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

We have the 3 values which we need for the LoadingComponent: Title, CurrentStep and TotalSteps. If one of those is changed we will invoke the OnProperyChanged event which then can be picked up by our LoadingComponent to refresh the state.

With this information we can model:

<div style="display: @DisplayClassLoading">
    <p>@LoadingConfiguration.Title</p>

    <div class="progress">
        <div class="progress-bar progress-bar-striped" role="progressbar"
             style="width: @LoadingConfiguration.GetPercentage()%"
             aria-valuenow="@LoadingConfiguration.CurrentStep"
             aria-valuemin="0"
             aria-valuemax="@LoadingConfiguration.TotalSteps"></div>
    </div>
</div>

<div style="display: @DisplayClassChildContent">
</div>

@code {
    private LoadingConfiguration LoadingConfiguration { get; set; } = new();

    private string DisplayClassLoading => LoadingConfiguration.IsLoading ? "initial" : "none";
    private string DisplayClassChildContent => LoadingConfiguration.IsLoading ? "none" : "initial";

    protected override void OnParametersSet()
    {
        // Everytime any property changes we update our UI
        LoadingConfiguration.PropertyChanged += (_, _) => StateHasChanged();
    }
}

What it does is basically, we will make the loading bar visible when the IsLoading flag is set to true and show all the information to model the loading bar itself including the title, steps and so on. Now you might wonder why I don't take something like @if(LoadingConfiguration.IsLoading) { } else { }. The reason is simple. If we set IsLoading to true it will show our loading bar, that is nice, but it will also remove our "real" component from the render tree. This is just how Blazor works. Therefore both components should be in the render tree all the time but both should not be visible at the same time.

Now the last bit: Add the "real" component placeholder:

<div style="display: @DisplayClassChildContent">
    @ChildCoontent
</div>

@code {
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    private LoadingConfiguration LoadingConfiguration { get; set; } = new();

    private string DisplayClassLoading => LoadingConfiguration.IsLoading ? "initial" : "none";
    private string DisplayClassChildContent => LoadingConfiguration.IsLoading ? "none" : "initial";

    protected override void OnParametersSet()
    {
        // Everytime any property changes we update our UI
        LoadingConfiguration.PropertyChanged += (_, _) => StateHasChanged();
    }
}

That is nice. Now we could use our component. But wait there is one thing missing! We need to pass down our LoadingConfiguration down to the ChildContent. To do so we will leverage CascadingValue:

<div style="display: @DisplayClassChildContent">
    <CascadingValue Value="@LoadingConfiguration">
        @ChildContent
    </CascadingValue>
</div>

That is all! We are done with our loading bar. Now we can use it in action. Lets create a new HeavyLoadComponent which does some stuff and propgates it changes via the LoadingConfiguration to its parent.

<h3>Roles:</h3>
<ul>
    @foreach (var role in _roles)
    {
        <li>@role</li>
    }
</ul>

@code {

    [CascadingParameter]
    public LoadingConfiguration LoadingConfiguration { get; set; } = default!;

    private string[] _roles = Array.Empty<string>();

    protected override async Task OnInitializedAsync()
    {
        // Here we will simulate some heavy work via Task.Delay
        LoadingConfiguration.TotalSteps = 3;
        LoadingConfiguration.Title = "Getting Data";
        LoadingConfiguration.IsLoading = true;

        await Task.Delay(1000);
        _roles = new[] { "Admin", "Co-Admin", "User" };

        LoadingConfiguration.CurrentStep++;
        LoadingConfiguration.Title = "Filter Data";

        await Task.Delay(1000);
        _roles = _roles.Where(s => s.Contains("Admin")).ToArray();

        LoadingConfiguration.CurrentStep++;
        LoadingConfiguration.Title = "Another step to prepare the matrix...";

        await Task.Delay(1000);

        LoadingConfiguration.CurrentStep++;
        LoadingConfiguration.Title = "Almost there...";

        await Task.Delay(1000);

        LoadingConfiguration.IsLoading = false;
    }
}

The LoadingConfiguration gets picked up by the child component via CascadingParameterAttribute. Now everytime we do a change the LoadingComponent will see the change via the event and therefore updates the UI to the new set values!

The usage is super simple:

<LoadingComponent>
    <HeavyWorkComponent></HeavyWorkComponent>
</LoadingComponent>

The result:

result

Awesome!

Conclusion

We wrote a nice component which does loading bar stuff for us without knowing the real child component. Also the child component does not have an idea about his parent.

Resources

Marking API's as obsolete or as experimental

Often times your API in your program or library evolves. So you will need a mechanism of telling that a specific API (an interface or just a simple method call) is obsolete and might be not there anymore in the next major version.

Also it can happen that you have a preview version of a API, which might not be rock-stable and the API-surface might change. How do we indicate that to the user?

.NET 8 and Blazor United / Server-side rendering

New .NET and new Blazor features. In this blog post, I want to highlight the new features that are hitting us with .NET 8 in the Blazor world. So let's see what's new.

Creating a ToolTip Component in Blazor

In this blog post we will create a ToolTip component in Blazor from scratch. We will use the Blazor WebAssembly template to create a new project. We will then add a ToolTip component to the project and use it in the Index page. We will also add some styling to the ToolTip component.

The advantage over using a library is that we can customize the component to our needs as well as keeping it simple! So let's get started!

An error has occurred. This application may no longer respond until reloaded. Reload x