Let's dive into Blazor's state management features, focusing on ways to share data and manage updates across components
Example: Shared Shopping Cart
Features Demonstrated:
Cascading Parameters: Passing data down a component hierarchy
State Management Services: Creating a centralized service to hold application state
Event Handling: Reacting to changes and updating the UI across components
Setup
Project: Create a new Blazor Server project (or reuse an existing one). You could name it "ShoppingCartApp"
Models
ShoppingCartItem.cs (Inside a Models folder):
namespace ShoppingCartApp.Models;
public class ShoppingCartItem
{
public string ProductName { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
}
Services
ShoppingCartService.cs (Create a Services folder):
namespace ShoppingCartApp.Services;
public class ShoppingCartService
{
public event Action OnChange; // Event for change notifications
private List<ShoppingCartItem> items = new List<ShoppingCartItem>();
public List<ShoppingCartItem> GetItems() => items.ToList(); // Read-only view
public void AddItem(ShoppingCartItem item)
{
items.Add(item);
OnChange?.Invoke();
}
// ... (potentially more methods like RemoveItem, UpdateQuantity, etc.)
}
Components
ShoppingCart.razor (Inside Shared folder):
@inject ShoppingCartService CartService
@implements IDisposable
<h3>Shopping Cart</h3>
<ul>
@foreach (var item in CartService.GetItems())
{
<li>@item.ProductName - @item.Price ($@item.Quantity)</li>
}
</ul>
@code {
protected override void OnInitialized()
{
CartService.OnChange += StateHasChanged; // Subscribe to changes
}
public void Dispose()
{
CartService.OnChange -= StateHasChanged; // Unsubscribe
}
}
ProductDisplay.razor (Inside Pages folder):
@page "/product-display"
@inject ShoppingCartService CartService
<CascadingValue Value="this">
<h1>Products</h1>
@* Display products and have "Add to Cart" buttons that call CartService.AddItem *@
</CascadingValue>
In your App.razor:
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
@code{
protected override void OnInitialized()
{
// Register the ShoppingCartService as a scoped service
builder.Services.AddScoped<ShoppingCartService>();
}
}