![]() |
VOOZH | about |
dotnet add package Nabs.Launchpad.Ui.Shell --version 10.0.226
NuGet\Install-Package Nabs.Launchpad.Ui.Shell -Version 10.0.226
<PackageReference Include="Nabs.Launchpad.Ui.Shell" Version="10.0.226" />
<PackageVersion Include="Nabs.Launchpad.Ui.Shell" Version="10.0.226" />Directory.Packages.props
<PackageReference Include="Nabs.Launchpad.Ui.Shell" />Project file
paket add Nabs.Launchpad.Ui.Shell --version 10.0.226
#r "nuget: Nabs.Launchpad.Ui.Shell, 10.0.226"
#:package Nabs.Launchpad.Ui.Shell@10.0.226
#addin nuget:?package=Nabs.Launchpad.Ui.Shell&version=10.0.226Install as a Cake Addin
#tool nuget:?package=Nabs.Launchpad.Ui.Shell&version=10.0.226Install as a Cake Tool
The Nabs Launchpad UI Shell library provides core UI services and messaging infrastructure for Blazor applications. This library implements global loading state management using the MVVM messaging pattern, enabling coordinated UI feedback across components without tight coupling.
Static service providing global loading state control with three simple methods: Start(), Stop(), and Spin(bool).
Message class carrying loading state changes through the MVVM messenger infrastructure.
// In a service or code-behind
public async Task LoadDataAsync()
{
// Start loading indicator
LoadingService.Start();
try
{
await FetchDataFromApiAsync();
// Process data...
}
finally
{
// Always stop loading indicator
LoadingService.Stop();
}
}
public async Task ProcessItemsAsync()
{
LoadingService.Spin(true); // Start loading
try
{
foreach (var item in items)
{
await ProcessAsync(item);
}
}
finally
{
LoadingService.Spin(false); // Stop loading
}
}
@page "/dashboard"
@implements IDisposable
@inject IMessenger Messenger
@if (isLoading)
{
<div class="loading-overlay">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
}
<div class="content">
</div>
@code {
private bool isLoading = false;
protected override void OnInitialized()
{
// Subscribe to loading state changes
Messenger.Register<LoadingStateMessage>(this, (recipient, message) =>
{
isLoading = message.Value;
InvokeAsync(StateHasChanged);
});
}
public void Dispose()
{
// Clean up subscription
Messenger.UnregisterAll(this);
}
}
@* GlobalProgress.razor *@
@implements IRecipient<LoadingStateMessage>
@implements IDisposable
@inject IMessenger Messenger
@if (IsLoading)
{
<div class="global-progress-overlay">
<div class="progress-spinner">
<span class="spinner-border spinner-border-lg"></span>
<p>Loading...</p>
</div>
</div>
}
@code {
private bool IsLoading { get; set; }
protected override void OnInitialized()
{
Messenger.RegisterAll(this);
}
public void Receive(LoadingStateMessage message)
{
IsLoading = message.Value;
InvokeAsync(StateHasChanged);
}
public void Dispose()
{
Messenger.UnregisterAll(this);
}
}
public class UserManagementViewModel : BaseItemViewModel<UserDto>
{
private readonly IUserService _userService;
public UserManagementViewModel(IUserService userService, IStringLocalizer localizer)
: base(localizer)
{
_userService = userService;
}
public async Task LoadUsersAsync()
{
LoadingService.Start();
try
{
var result = await _userService.GetAllUsersAsync();
if (result.IsSuccess)
{
// Process users
}
}
finally
{
LoadingService.Stop();
}
}
}
public class DataSyncService
{
public async Task SyncAllDataAsync()
{
LoadingService.Start();
try
{
// Sync users - inner loading states are ignored
await SyncUsersAsync();
// Sync orders
await SyncOrdersAsync();
// Sync products
await SyncProductsAsync();
}
finally
{
LoadingService.Stop();
}
}
private async Task SyncUsersAsync()
{
// This won't interfere with outer loading state
LoadingService.Start();
try
{
await Task.Delay(1000);
}
finally
{
LoadingService.Stop();
}
}
}
public class LoadingHttpMessageHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
LoadingService.Start();
try
{
return await base.SendAsync(request, cancellationToken);
}
finally
{
LoadingService.Stop();
}
}
}
// Register in Program.cs
builder.Services.AddTransient<LoadingHttpMessageHandler>();
builder.Services.AddHttpClient("api")
.AddHttpMessageHandler<LoadingHttpMessageHandler>();
Static service for controlling global loading state.
public static void Start()
Starts the global loading indicator by sending a loading state message with value true.
Usage:
LoadingService.Start();
public static void Stop()
Stops the global loading indicator by sending a loading state message with value false.
Usage:
LoadingService.Stop();
public static void Spin(bool value)
Sets the loading state to the specified value.
Parameters:
value: true to start loading, false to stopUsage:
LoadingService.Spin(isLoading);
Message class for communicating loading state changes.
public LoadingStateMessage(bool isLoading)
Creates a new loading state message.
Parameters:
isLoading: The loading state valuepublic bool Value { get; }
Gets the loading state value (inherited from ValueChangedMessage<bool>).
The library uses CommunityToolkit.Mvvm's WeakReferenceMessenger to implement a publish-subscribe pattern:
LoadingService publishes LoadingStateMessage instancesLoadingService is implemented as a static class providing a singleton-like behavior without requiring dependency injection registration.
When a loading state changes, all registered subscribers receive the message simultaneously, enabling coordinated UI updates.
// Good
LoadingService.Start();
try
{
await DoWorkAsync();
}
finally
{
LoadingService.Stop();
}
// Bad - loading might not stop on exception
LoadingService.Start();
await DoWorkAsync();
LoadingService.Stop();
// Implement IDisposable in components
public void Dispose()
{
Messenger.UnregisterAll(this);
}
Messenger.Register<LoadingStateMessage>(this, (r, m) =>
{
isLoading = m.Value;
InvokeAsync(StateHasChanged); // Required for Blazor components
});
Create a single global progress component rather than multiple loading indicators throughout the application.
Be careful with nested operations that both control loading state. Consider using a counter-based approach for complex scenarios.
public class MyViewModel : BaseItemViewModel<MyDto>
{
protected override async Task LoadDataAsync()
{
LoadingService.Start();
try
{
// Load data
}
finally
{
LoadingService.Stop();
}
}
}
The Syncfusion UI Shell library includes a pre-built GlobalProgress component that subscribes to loading state messages.
Portal applications can use the loading service in middleware or during authentication.
The messenger uses weak references, which means:
public void Dispose()
{
// Explicitly unregister for cleaner resource management
Messenger.UnregisterAll(this);
}
The loading service is thread-safe:
// Safe to call from multiple threads
await Task.WhenAll(
Task.Run(() => Operation1()),
Task.Run(() => Operation2()),
Task.Run(() => Operation3())
);
async Task Operation1()
{
LoadingService.Start();
try { /* work */ }
finally { LoadingService.Stop(); }
}
[Fact]
public async Task Service_ShowsLoadingState()
{
// Arrange
var messenger = WeakReferenceMessenger.Default;
var loadingStates = new List<bool>();
messenger.Register<LoadingStateMessage>(this, (r, m) =>
{
loadingStates.Add(m.Value);
});
var service = new MyService();
// Act
await service.ProcessAsync();
// Assert
loadingStates.Should().Contain(true); // Started loading
loadingStates.Should().Contain(false); // Stopped loading
// Cleanup
messenger.UnregisterAll(this);
}
[Fact]
public void Component_RespondsToLoadingState()
{
// Arrange
using var ctx = new TestContext();
var component = ctx.RenderComponent<MyComponent>();
// Act - Send loading message
WeakReferenceMessenger.Default.Send(new LoadingStateMessage(true));
// Assert
component.Find(".loading-indicator").Should().NotBeNull();
// Act - Stop loading
WeakReferenceMessenger.Default.Send(new LoadingStateMessage(false));
// Assert
component.FindAll(".loading-indicator").Should().BeEmpty();
}
// Define custom message
public class LoadingProgressMessage : ValueChangedMessage<(bool IsLoading, int Percentage)>
{
public LoadingProgressMessage(bool isLoading, int percentage)
: base((isLoading, percentage)) { }
}
// Service with progress
public static class ProgressLoadingService
{
public static void ReportProgress(int percentage)
{
WeakReferenceMessenger.Default.Send(
new LoadingProgressMessage(true, percentage));
}
}
// Different loading contexts
public enum LoadingContext
{
Global,
Grid,
Form
}
public class ContextualLoadingMessage : ValueChangedMessage<(LoadingContext Context, bool IsLoading)>
{
public ContextualLoadingMessage(LoadingContext context, bool isLoading)
: base((context, isLoading)) { }
}
Issue: Loading state changes but UI doesn't update
Solutions:
InvokeAsync(StateHasChanged) in the message handlerIssue: Loading indicator stays visible
Solutions:
Stop() is called in a finally blockStart() without matching Stop()Issue: Components not being garbage collected
Solutions:
IDisposable and call Messenger.UnregisterAll(this)this in lambda expressions that outlive the component| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
Showing the top 3 NuGet packages that depend on Nabs.Launchpad.Ui.Shell:
| Package | Downloads |
|---|---|
|
Nabs.Launchpad.Core.ViewModels
Package Description |
|
|
Nabs.Launchpad.Ui.Shell.Blazor.Sf
Package Description |
|
|
Nabs.Launchpad.Core.Portal
Package Description |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.250 | 120 | 6/23/2026 |
| 10.0.249 | 132 | 6/6/2026 |
| 10.0.248 | 137 | 6/6/2026 |
| 10.0.247 | 130 | 6/6/2026 |
| 10.0.246 | 138 | 6/6/2026 |
| 10.0.242 | 126 | 6/4/2026 |
| 10.0.241 | 128 | 6/4/2026 |
| 10.0.240 | 127 | 6/4/2026 |
| 10.0.239 | 134 | 6/3/2026 |
| 10.0.234 | 131 | 5/31/2026 |
| 10.0.233 | 135 | 5/31/2026 |
| 10.0.232 | 123 | 5/30/2026 |
| 10.0.230 | 129 | 5/30/2026 |
| 10.0.229 | 123 | 5/30/2026 |
| 10.0.228 | 131 | 5/30/2026 |
| 10.0.226 | 153 | 4/26/2026 |
| 10.0.221 | 142 | 2/3/2026 |
| 10.0.220 | 153 | 1/14/2026 |
| 10.0.219 | 155 | 1/5/2026 |
| 10.0.218 | 156 | 1/4/2026 |