I want to put up a Loading… overlay while a page is loading. If everything was in my razor page then I could make it visible as the first line in OnInitializedAsync()
and hide it as the last line.
But I have children components in the page. And as they all also have their own OnInitializedAsync()
, and that is async for all of them, they complete in a random order.
So, for making the overlay visible, is the containing page’s OnInitializedAsync
the first to be called?
And is OnAfterRenderAsync
where I should then hide it? Or can I do so in OnInitialized
(no Async
)? Or somewhere else?
I need this not only for the UI letting the user know the page is loading, but also for my bUnit tests to have it WaitForState()
until the page is fully rendered. I can test for the IsLoading property to be false.
Here's a demo to show how the render sequence works and how the LoadingOverlay can work with sub-components.
Take the LoadingOverlay
from the other answer:
<div class="@_css">
<div class="container text-center">
<div class="alert alert-warning m-5 p-5">We're experiencing very high call volumes today [nothing out of the norm now].
You're at call position 199. You're business, not you, is important to us!</div>
</div>
</div>
@if(this.ChildContent is not null)
{
@this.ChildContent
}
@code {
private string _css => this.IsLoading ? "loading" : "loaded";
[Parameter] public bool IsLoading { get; set; }
[Parameter] public RenderFragment? ChildContent { get; set; }
}
Add the Blazr.BaseComponents package to the project. DocumentedComponentBase
is used as the base component for the components to document their lifecycle and event sequences.
Add WeatherList.Razor
@inherits Blazr.BaseComponents.ComponentBase.DocumentedComponentBase
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in _forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
<LoadingOverlay IsLoading="_loading" />
@code {
[Parameter] public IEnumerable<WeatherForecast>? Forecasts { get; set; }
private bool _loading = true;
private IEnumerable<WeatherForecast> _forecasts = Enumerable.Empty<WeatherForecast>();
protected override async Task OnInitializedAsync()
{
await Task.Delay(1000);
_forecasts = this.Forecasts ?? Enumerable.Empty<WeatherForecast>();
_loading = false;
}
}
Update FetchData
:
@page "/fetchdata"
@inject WeatherForecastService ForecastService
@inherits Blazr.BaseComponents.ComponentBase.DocumentedComponentBase
<PageTitle>Weather forecast</PageTitle>
<LoadingOverlay IsLoading="_loading" />
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from a service.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<WeatherList Forecasts="forecasts" />
}
@code {
private WeatherForecast[]? forecasts;
private bool _loading => forecasts == null;
protected override async Task OnInitializedAsync()
{
await Task.Delay(1000);
forecasts = await ForecastService.GetForecastAsync(DateOnly.FromDateTime(DateTime.Now));
}
}
If you now run this code you will see the following output to the console by DocumentedComponentBase
:
Note that FetchData
runs it's OnInitialized sequence to completion and renders and runs it's first OnAfterRenderAsync
before WeatherList
is even created.
===========================================
4755 - FetchData => Component Initialized
4755 - FetchData => Component Attached
4755 - FetchData => SetParametersAsync Started
4755 - FetchData => OnInitialized sequence Started
4755 - FetchData => Awaiting Task completion
4755 - FetchData => StateHasChanged Called
4755 - FetchData => Render Queued
4755 - FetchData => Component Rendered
4755 - FetchData => OnAfterRenderAsync Started
4755 - FetchData => OnAfterRenderAsync Completed
4755 - FetchData => OnInitialized sequence Completed
4755 - FetchData => OnParametersSet Sequence Started
4755 - FetchData => StateHasChanged Called
4755 - FetchData => Render Queued
4755 - FetchData => Component Rendered
===========================================
d15e - WeatherList => Component Initialized
d15e - WeatherList => Component Attached
d15e - WeatherList => SetParametersAsync Started
d15e - WeatherList => OnInitialized sequence Started
d15e - WeatherList => Awaiting Task completion
d15e - WeatherList => StateHasChanged Called
d15e - WeatherList => Render Queued
d15e - WeatherList => Component Rendered
4755 - FetchData => OnParametersSet Sequence Completed
4755 - FetchData => SetParametersAsync Completed
4755 - FetchData => OnAfterRenderAsync Started
4755 - FetchData => OnAfterRenderAsync Completed
d15e - WeatherList => OnAfterRenderAsync Started
d15e - WeatherList => OnAfterRenderAsync Completed
d15e - WeatherList => OnInitialized sequence Completed
d15e - WeatherList => OnParametersSet Sequence Started
d15e - WeatherList => StateHasChanged Called
d15e - WeatherList => Render Queued
d15e - WeatherList => Component Rendered
d15e - WeatherList => OnParametersSet Sequence Completed
d15e - WeatherList => SetParametersAsync Completed
d15e - WeatherList => OnAfterRenderAsync Started
d15e - WeatherList => OnAfterRenderAsync Completed
The above documented sequence demonstrates that there is no way you can set a flag in FetchData
that shows a Loading message that you can guarantee will not complete before sub-components start and run their OnInitialized
sequences.