Blazor Infinite Scrolling
Blazor Infinite Scrolling
Blazor: Blazor is a web framework developed by Microsoft that allows developers to build interactive web UIs using C# instead of JavaScript. It enables full-stack web development with .NET.
Infinite Scrolling: Infinite scrolling is a technique where new data is loaded and displayed as the user scrolls down the page. It provides a smooth and continuous browsing experience without the need for manual pagination.
Razor Components: Razor Components is a part of Blazor that allows developers to build reusable UI components using a combination of HTML and C#. These components can be used to create dynamic and interactive web applications.
InfiniteScrollingItemsProviderRequest.cs
The InfiniteScrollingItemsProviderRequest
class is defined within the BlazorAppInfiniteScrolling.Components
namespace. It is a sealed class, meaning it cannot be inherited from. The class has two properties: StartIndex
and CancellationToken
.
namespace BlazorAppInfiniteScrolling.Components;
public sealed class InfiniteScrollingItemsProviderRequest
{
public InfiniteScrollingItemsProviderRequest(int startIndex, CancellationToken cancellationToken)
{
StartIndex = startIndex;
CancellationToken = cancellationToken;
}
public int StartIndex { get; }
public CancellationToken CancellationToken { get; }
}
public delegate Task<IEnumerable<int>> ItemsProviderRequestDelegate(InfiniteScrollingItemsProviderRequest request);
public delegate Task<IEnumerable<T>> ItemsProviderRequestDelegate<T>(InfiniteScrollingItemsProviderRequest request);
The InfiniteScrollingItemsProviderRequest
class has a constructor that takes two parameters: startIndex
and cancellationToken
. The startIndex
parameter represents the index from which the data should be loaded, and the cancellationToken
parameter is used to cancel the data loading operation if needed.
The class also has two properties: StartIndex
and CancellationToken
. The StartIndex
property is a read-only property that returns the value passed in the constructor. The CancellationToken
property is also read-only and returns the cancellation token
passed in the constructor.
InfiniteScrolling.razor
Several classes
and delegates
that are essential for implementing infinite scrolling in Blazor.
@namespace BlazorAppInfiniteScrolling.Components
@typeparam T
@foreach (var item in _items)
{
@ItemTemplate(item)
}
@if (_loading)
{
@LoadingTemplate
}
@if (!_enumerationCompleted)
{
<div @ref="_lastItemIndicator" style="height:1px;flex-shrink:0"></div>
}
The InfiniteScrollingItemsProviderRequest
class represents a request for fetching items for infinite scrolling. It takes the start index
and a cancellation token
as parameters.
The ItemsProviderRequestDelegate
delegate defines a method signature for fetching items based on the InfiniteScrollingItemsProviderRequest
. It can be used to provide a custom implementation for fetching data.
InfiniteScrolling.razor.cs
The InfiniteScrolling
component is a generic component that takes a type parameter T
, which represents the type of the items to be displayed. It inherits from the ComponentBase
class and implements the IAsyncDisposable
interface.
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BlazorAppInfiniteScrolling.Components;
public partial class InfiniteScrolling<T> : ComponentBase, IAsyncDisposable
{
private List<T> _items = new();
private ElementReference _lastItemIndicator;
private DotNetObjectReference<InfiniteScrolling<T>>? _currentComponentReference;
private IJSObjectReference? _module;
private IJSObjectReference? _instance;
private bool _loading = false;
private bool _enumerationCompleted = false;
private CancellationTokenSource? _loadItemsCts;
[Inject]
private IJSRuntime? JSRuntime { get; set; }
[Parameter]
public ItemsProviderRequestDelegate<T>? ItemsProvider { get; set; }
[Parameter]
public RenderFragment<T>? ItemTemplate { get; set; }
[Parameter]
public RenderFragment? LoadingTemplate { get; set; }
[JSInvokable]
public async Task LoadMoreItems()
{
if (_loading)
return;
_loading = true;
try
{
_loadItemsCts ??= new CancellationTokenSource();
StateHasChanged();
try
{
var newItems = await ItemsProvider(new InfiniteScrollingItemsProviderRequest(_items.Count, _loadItemsCts.Token));
var previousCount = _items.Count;
_items.AddRange(newItems);
if (_items.Count == previousCount)
{
_enumerationCompleted = true;
}
else
{
await _instance.InvokeVoidAsync("onNewItems");
}
}
catch (OperationCanceledException oce) when (oce.CancellationToken == _loadItemsCts.Token)
{
}
}
finally
{
_loading = false;
}
StateHasChanged();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
_module = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./infinite-scrolling.js");
_currentComponentReference = DotNetObjectReference.Create(this);
_instance = await _module.InvokeAsync<IJSObjectReference>("initialize", _lastItemIndicator, _currentComponentReference);
}
}
public async ValueTask DisposeAsync()
{
if (_loadItemsCts != null)
{
_loadItemsCts.Dispose();
_loadItemsCts = null;
}
if (_instance != null)
{
await _instance.InvokeVoidAsync("dispose");
await _instance.DisposeAsync();
_instance = null;
}
if (_module != null)
{
await _module.DisposeAsync();
}
_currentComponentReference?.Dispose();
}
}
The component
has several private fields, including a list of items _items
, a reference to the last item indicator _lastItemIndicator
, a reference to the current component _currentComponentReference
, and references to the JavaScript module _module
and instance _instance
. It also has boolean flags _loading and _enumerationCompleted
to track the loading state and whether the enumeration of items is completed.
The component has several properties, including an injected IJSRuntime
for JavaScript interop, a ItemsProvider
delegate for providing the items, an ItemTemplate for rendering the items, and a LoadingTemplate
for rendering the loading indicator.
The component also defines the LoadMoreItems
method, which is invoked when the user scrolls to the end of the list or grid. It handles the loading of more items by calling the ItemsProvider
delegate and updating the UI.
The OnAfterRenderAsync
method is overridden to initialize the JavaScript
module and instance when the component is first rendered. It imports the JavaScript module, creates a reference to the current component, and initializes the JavaScript instance with the last item indicator and the component reference.
The DisposeAsync
method is implemented to dispose of any resources used by the component, such as cancellation tokens
, JavaScript instances, and references.
infinite-scrolling.js
The initialize
function takes two parameters: lastItemIndicator
and componentInstance
. The lastItemIndicator
is the element that indicates the last item in the list, and the componentInstance
is the instance of the Blazor component.
export function initialize(lastItemIndicator, componentInstance) {
const options = {
root: findClosestScrollContainer(lastItemIndicator),
rootMargin: '0px',
threshold: 0,
};
const observer = new IntersectionObserver(async (entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
observer.unobserve(lastItemIndicator);
await componentInstance.invokeMethodAsync("LoadMoreItems");
}
}
}, options);
observer.observe(lastItemIndicator);
return {
dispose: () => dispose(observer),
onNewItems: () => {
observer.unobserve(lastItemIndicator);
observer.observe(lastItemIndicator);
},
};
}
function dispose(observer) {
observer.disconnect();
}
function findClosestScrollContainer(element) {
while (element) {
const style = getComputedStyle(element);
if (style.overflowY !== 'visible') {
return element;
}
element = element.parentElement;
}
return null;
}
The initialize
function creates an Intersection Observer
with the specified options and attaches a callback function
to it. When the last item indicator element intersects with the viewport
, the callback function is triggered
. It then unobserves the last item indicator
, and asynchronously invokes the LoadMoreItems
method on the Blazor component instance.
The dispose function
is used to disconnect the Intersection Observer
when it is no longer needed. The findClosestScrollContainer
function is a helper function that finds the closest scrollable container
element for the given element.
Source
Full source code is available at this repository in GitHub:
https://github.com/akifmt/DotNetCoding/tree/main/src/BlazorAppInfiniteScrolling
comments powered by Disqus