Thomas Adrian
Thomas Adrian

Reputation: 3636

How to show a loading indicator with Blazor QuickGrid

The QuickGrid component in Blazor takes an IQueryable object which is defered so the execution of the query is done inside the QuickGrid

How can I display a loading indicator while the QuickGrid does its magic?

@using Microsoft.AspNetCore.Components.QuickGrid
<QuickGrid  Items="results" />

private IQueryable results{ get;set;}
protected override void OnInitialized()
{
     results = ActivityService.GetActivities();
}

thanks

In the animation below you can see that a display message is showed for 1 sec before the quickgrid show the columns (If I add a Task.Delay(1000). but I still have to wait for the data to load without a display message

enter image description here

Upvotes: 0

Views: 980

Answers (4)

Tim Yates
Tim Yates

Reputation: 1

I am pretty new at this, maybe this can help you out, this is the way I did it.

You can use a Modal Component, while loading, display the Modal until quickGrid is loaded

Make the Modal component dynamic

<!-- Modal -->
<div class="modal fade show" id="exampleModalLive" tabindex="-1" aria-labelledby="exampleModalLiveLabel" aria-model="true" role="dialog" style="display:block;">
    <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable @SizeClass">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title" id="staticBackdropLabel">@Title</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="OnCancelClick"></button>
            </div>
            <div class="modal-body">
                @ChildContent
            </div>
            <div class="modal-footer d-flex justify-content-between">
                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="OnCancelClick">Close</button>
                <button type="button" class="btn btn-primary" @onclick="OnActionButtonClick">@ActionButtonText</button>
            </div>
        </div>
    </div>
</div>
<div class="modal-backdrop fade show"></div>
@code {
    [Parameter, EditorRequired]
    public string Title { get; set; }

    [Parameter]
    public ModalSize Size { get; set; } = ModalSize.Default;

    [Parameter]
    public string ActionButtonText { get; set; } = "Ok";

    [Parameter, EditorRequired]
    public RenderFragment ChildContent { get; set; }

    [Parameter]
    public EventCallback OnActionButtonClick { get; set; }

    [Parameter]
    public EventCallback OnCancelClick { get; set; }

 
    private string SizeClass => Size switch
    {
        ModalSize.Small => "modal-sm",
        ModalSize.Default => "",
        ModalSize.Large => "modal-lg",
        ModalSize.ExtraLarge => "modal-xl",
        _ => ""
    };

}

Call the Modal in your .Razor Page

@if(_showModal)
 {
<Modal Title="String" ActionButtonText="Ok" Size="ModalSize.Large"
    OnActionButtonClick="() => _showModal = false"
    OnCancelClick="() => _showModal = false">
    
    @foreach (var d in _viewingData)
    {
        <div class="mb-3 shadow p-3 border border-start border-sucess">
            @d.Text
        </div>
    }

</Modal>
}

Make the AppState.ShowLoader("Loading Data Please standby") call when OnInitialize in Razor Component Showing the Grid

private bool _isBusy = false;
private string? _errroMessage;
   
private async Task TaskAsync()
 {
    _error = null;
    _isBusy = true;

    try
    {
        AppState.ShowLoader("Loading Data");
        var apiResponse = await AuthAPI.TaskAsync(_model);

        if (!apiResponse.IsSuccess)
        {
            _error = apiResponse.ErrorMessage;
            return;
        }
        _showSuccessAlert = true;
    }
    catch (Exception ex)
    {
        _error = ex.Message;
    }
    finally
    {
        _isBusy = false;
        AppState.HideLoader();
    }
}

Create AppState

namespace NameSpace.Web.ApplicationState
{
    public class AppState : IAppState
    {
        public string? LoadingText { get; private set; }

        public event Action? OnToggleLoader;
        public event Action<string>? OnShowError;

        public void HideLoader()
        {
            LoadingText = null;
            OnToggleLoader?.Invoke();
        }

        public void ShowLoader(string loadingText)
        {
            LoadingText = loadingText;
            OnToggleLoader?.Invoke();
        }

        public void ShowError(string errorText) =>
        OnShowError?.Invoke(errorText);
    }
}

Create Interface from AppState

 public interface IAppState
 {
      string? LoadingText { get; }
      void ShowLoader(string loadingText);
      void HideLoader();
      event Action? OnToggleLoader;
      void ShowError(string errorText);
  }

Inject IAppState into the Razor component

@inject IAppSate AppState

Make sure to register

builder.Services.AddSingleton<IAppState, AppState>();

Upvotes: 0

Bernard Lazo
Bernard Lazo

Reputation: 1

Had the exact same problem, datasource object becomes not null before entirely loading the data, resulting in delay to load with no "Loading ..." warning. This can be very annoying to the end user (thinking nothing is happening). This code did the magic for me:

private bool Busy = true;

private POCdbContext? Context { get; set; }

private List<Customer> CustomerList { get; set; } = new List<Customer>();

IQueryable<Customer>? Customers { get; set; }

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        var task = GetData();

        await task.ContinueWith(r =>
        {
            if (r.IsCompleted)
            {
                var result = r.Result;

                Dispatcher.CreateDefault().InvokeAsync(() =>
                {
                    // do stuff here
                    Busy = false;
                    // Refresh UI
                    InvokeAsync(StateHasChanged);
                });
            }
        });
    }
}

public async Task<List<Customer>> GetData()
{
    List<Customer> customers;

    Context = DbFactory.CreateDbContext();

    customers = await Context.Customers.Where(x => x.Name.Contains(TitleFilter)).ToListAsync();

    CustomerList = customers;

    Customers = CustomerList.AsQueryable();

    return customers;
}

bool Busy is the flag that controls wether QuickGrid component renders or not (standard if statement in the html section of razor page -ommited for brevity-). Page rendermode is InteractiveServer.

Adapted the original code by @Surinder Singh InThisAnswer

Hope this helps, took me a long time to find this.

UPDATE: Lol, turns out I overcomplicated things. While the above code works fine, found a way simpler solution: just add at the top of the razor page the StreamRendering attribute

@attribute [StreamRendering]

The rendering logic stays the same, an if statement that renders "loading data..." label if a property (that contains the data) is null and the data table if it is not null

Upvotes: 0

emorning
emorning

Reputation: 26

You didn't say what render mode you're using, but if you're using SSR then I think you can stream the results. Note: I haven't created a test to verify what I'm about to say, and I'm still learning this stuff myself.

That said, what I think what's happening is that...

  • when OnInitializedAsync completes your page is rerendered with non-null results.
  • QuickGrid re-renders itself, but the queryable provides results slowly, so it takes some time.
  • When QuickGrid rendering is complete you see the results in your page, but you dont see any results until QuickGrid has rendered the entire table.

But if you're using SSR I *think you can change that by putting this at the top of your page...

@attribute [StreamRendering]

This will cause the DOM elements rendered on the server to stream to the client as they're rendered.

The result should be that the first row should display quickly, negating the need for a loading indicator at this point. More rows are displayed as they are rendered.

Upvotes: 1

MrC aka Shaun Curtis
MrC aka Shaun Curtis

Reputation: 30177

It isn't built into DataGrid and I don't see any simple way to detect the state of the internal iteration of the provided IQueryable.

You will need to customize the code. I'll quote the documentation below.

QuickGrid isn't intended to replace advanced datagrid components such as those from commercial component vendors. Instead, the purpose is:

  • To provide a convenient, simple, and flexible datagrid component for Blazor developers with the most common needs
  • To provide a reference architecture and performance baseline for anyone building Blazor datagrid components. Feel free to build on this, or simply copy code from it.

It's not a goal to add all the features that full-blown commercial grids tend to have, for example hierarchical rows, drag-to-reorder columns, or Excel-like range selections. If you need those, continue using commercial grids or other open-source libraries. Currently we're only adding the most core grid functionality and things needed for the key fundamentals.

Upvotes: 1

Related Questions