Componente de barra de carregamento reutilizável no Blazor
Componente de barra de carregamento reutilizável no Blazor
Às vezes temos uma tarefa que demora um pouco mais. Imagine que você deseja obter uma grande quantidade de dados, processar esses dados e torná-los visíveis para o usuário. Se isso demorar um pouco, nada realmente acontece aos olhos do usuário. Então vamos criar um componente de barra de carregamento reutilizável no Blazor.
O componente da barra de carregamento
Vamos mantê-lo simples para o início. Sinta-se à vontade para ajustar tudo o que quiser. Como o Blazor vem com o Bootstrap, usaremos o Bootstrap para nossa própria barra de carregamento. Então, vamos criar um novo componente razor de carregamento:
<!-- 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>
}
Precisamos de algumas coisas aqui para fazer:
- Temos que mudar a visibilidade do nosso componente da barra de carregamento e do nosso componente renderizado "real". Como disse, vamos mantê-lo muito simples. Você pode ajustar isso às suas necessidades.
- Nós não queremos conhecer o componente "real" dos olhos do LoadingComponent caso contrário não seria realmente reutilizável.
- O componente "real" deve definir o título, o total de etapas e a etapa atual de dentro de seu componente.
O primeiro passo é que precisamos de um container que modele alguns dos requisitos. Esse contêiner é hospedado pelo LoadingComponent e é transmitido para o componente real. O LoadingComponent também precisa ouvir as alterações quando o contêiner é alterado. Para todos vocês que trabalharam com WPF ou UWP vão se sentir "em casa". Usaremos a interface INotifyPropertyChanged.
Este é o nosso modelo:
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));
}
}
Temos os 3 valores que precisamos para o LoadingComponent: Title, CurrentStep e TotalSteps. Se um deles for alterado, invocaremos o evento OnProperyChanged, que pode ser selecionado pelo nosso LoadingComponent para atualizar o estado.
Com essas informações podemos modelar:
<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();
}
}
O que ele faz é basicamente tornar a barra de carregamento visível quando o sinalizador IsLoading estiver definido como verdadeiro e mostrar todas as informações para modelar a própria barra de carregamento, incluindo o título, etapas e assim por diante. Agora você pode se perguntar por que eu não uso algo como @if(LoadingConfiguration.IsLoading) { } else { }. A razão é simples. Se definirmos IsLoading como verdade, ele mostrará nossa barra de carregamento, o que é bom, mas também removerá nosso componente "real" da árvore de renderização. É assim que o Blazor funciona. Portanto, ambos os componentes devem estar na árvore de renderização o tempo todo, mas não devem estar visíveis ao mesmo tempo.
Agora, a última parte: adicione o espaço reservado do componente "real":
<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();
}
}
Isso é bom. Agora podemos usar nosso componente. Mas espere, está faltando uma coisa! Precisamos passar nosso LoadingConfiguration para ChildContent. Para fazer isso, vamos aproveitar o CascadingValue:
<div style="display: @DisplayClassChildContent">
<CascadingValue Value="@LoadingConfiguration">
@ChildContent
</CascadingValue>
</div>
Isso é tudo! Terminamos a nossa barra de carregamento. Agora podemos usá-lo em ação. Vamos criar um novo HeavyLoadComponent que faz algumas coisas e propaga suas mudanças através do LoadingConfiguration para seu pai.
<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;
}
}
O LoadingConfiguration é selecionado pelo componente filho por meio de CascadingParameterAttribute. Agora, toda vez que fizermos uma alteração, o LoadingComponent verá a alteração por meio do evento e, portanto, atualizará a interface do usuário para os novos valores definidos!
O uso é super simples:
<LoadingComponent>
<HeavyWorkComponent></HeavyWorkComponent>
</LoadingComponent>
O resultado:
Impressionante!
Conclusão
Nós escrevemos um bom componente que carrega coisas de barra para nós sem conhecer o componente filho real. Além disso, o componente filho não tem ideia sobre seu pai.
Recursos
- Este exemplo no GitHub