dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.44k stars 10.02k forks source link

Virtualize component Placeholder has no effect #57929

Closed AndrewBorst closed 1 month ago

AndrewBorst commented 1 month ago

Is there an existing issue for this?

Describe the bug

Adding the Placeholder tag to the Virtualize component has no effect. I could not make a working example or find any code online that worked. I tried testing locally on two different machines and neither worked. I also tried the EmptyContent and that didn't work either. In older issue, somebody pointed out this only works with ItemsProvider which I am trying to use.

Expected Behavior

How it should work:

  1. When scrolling through virtualized content, instead of showing empty space for unloaded items, the Placeholder template is rendered.
  2. As actual items are loaded, they replace the placeholders.

Steps To Reproduce

So a minimal example is on the FetchData page of Repo. On the main branch, I have a working alternative for showing a message while waiting for the items to load.

Exceptions (if any)

No response

.NET Version

8.0.108

Anything else?

No response

MackinnonBuck commented 1 month ago

Hi, @AndrewBorst.

Until the items provider returns its first ItemsProviderResult, the <Virtualize> component doesn't know how many items to render, and therefore doesn't know how many placeholders to render.

In your repro app, the first ItemsProviderResult is returned after two seconds, and it indicates that there are 10 total items, but it simultaneously returns however many items were requested in ItemsProviderRequest.Count. As long as the requested count is larger than 10, you will never see any placeholders.

If you change the totalItemCount from 10 to say, 1000, you'll notice that placeholders appear when you scroll.

When loading data asynchronously, it's not super common to know the total item count before the initial items are fetched, which is why the API is designed this way. If you want some kind of loading experience for the initial set of items, you could render a separate loading indicator until the first set of items are fetched. This is what we would recommend.


However, if you really want to render some hard-coded number of initial placeholders, despite this being possibly different from the actual item count, you could make the first ItemsProviderResult return an empty list of items and specify a totalItemCount matching the desired number of placeholders. Then, you can manually request more items via Virtualize.RefreshDataAsync. I've included an example of this below (but I've hidden it by default because I don't want to highlight it as the "answer" - I'd anticipate it's not common to need to do this).

Example ```razor @* ... *@ @code { private const int InitialPlaceholderCount = 10; private Virtualize _virtualize = null!; private bool _hasProvidedInitialPlaceholders; private async ValueTask> LoadForecastsAsync(ItemsProviderRequest request) { if (!_hasProvidedInitialPlaceholders) { _hasProvidedInitialPlaceholders = true; _ = LoadActualItemsAsync(); return new(items: [], totalItemCount: InitialPlaceholderCount); async Task LoadActualItemsAsync() { await Task.Yield(); // Defer the call to 'RefreshDataAsync()` to avoid cancelling the current request await _virtualize.RefreshDataAsync(); // Refresh the data, which will call 'LoadForecastsAsync()' again StateHasChanged(); // Re-render the component } } // ... load the actual items ... return new(actualItems, actualItemCount); } } ```

Could you please let us know if this addresses your concerns? We'll close out this issue if there's not a framework problem here. Thanks!

AndrewBorst commented 1 month ago

@MackinnonBuck Thank you for the explanation and sample code, it makes sense now that the Placeholder is at the item-level and not the component-level. Please close out the issue.