Dziedziczenie komponentów w Blazorze

· 3 min czytania

I was building a project that had a bunch of form pages, and every single one had the same loading state logic, the same error handling, and the same toast notifications. Copy-pasting all of that felt wrong, so I looked into component inheritance in Blazor. Turns out it’s pretty straightforward since Blazor components are just C# classes.

The basics

Every Blazor component inherits from ComponentBase by default. You can create your own base class that extends ComponentBase and then have your components inherit from that.

Let’s say most of our pages need loading state and error handling. We can create a base class:

using Microsoft.AspNetCore.Components;

public abstract class PageBase : ComponentBase
{
    protected bool IsLoading { get; set; } = true;
    protected string? ErrorMessage { get; set; }

    protected async Task LoadDataAsync(Func<Task> action)
    {
        try
        {
            IsLoading = true;
            ErrorMessage = null;
            await action();
        }
        catch (Exception ex)
        {
            ErrorMessage = ex.Message;
        }
        finally
        {
            IsLoading = false;
            StateHasChanged();
        }
    }
}

Using the base class

Now in any page component, instead of inheriting from ComponentBase, we inherit from our PageBase:

@page "/users"
@inherits PageBase

@if (IsLoading)
{
    <div class="spinner"></div>
}
else if (ErrorMessage is not null)
{
    <div class="alert alert-danger">@ErrorMessage</div>
}
else
{
    <ul>
        @foreach (var user in users)
        {
            <li>@user.Name</li>
        }
    </ul>
}

@code {
    private List<User> users = new();

    protected override async Task OnInitializedAsync()
    {
        await LoadDataAsync(async () =>
        {
            users = await Http.GetFromJsonAsync<List<User>>("api/users");
        });
    }
}

The @inherits PageBase directive is the key. It tells Blazor to use our base class instead of the default ComponentBase. Now we get IsLoading, ErrorMessage, and LoadDataAsync() for free in every page that inherits from it.

Injecting services in the base class

You can also inject services in the base class so they’re available to all child components:

public abstract class PageBase : ComponentBase
{
    [Inject]
    protected NavigationManager Navigation { get; set; } = default!;

    [Inject]
    protected IToastService Toast { get; set; } = default!;

    protected bool IsLoading { get; set; } = true;
    protected string? ErrorMessage { get; set; }

    protected void NavigateBack() => Navigation.NavigateTo("javascript:history.back()");

    protected void ShowSuccess(string message) => Toast.ShowSuccess(message);
}

Every component that inherits from PageBase now has access to Navigation, Toast, NavigateBack(), and ShowSuccess() without having to inject anything.

Going deeper with generic base classes

You can even make generic base classes for common CRUD patterns:

public abstract class CrudPageBase<T> : PageBase
{
    protected List<T> Items { get; set; } = new();
    protected T? SelectedItem { get; set; }

    protected abstract Task<List<T>> FetchItems();
    protected abstract Task DeleteItem(T item);

    protected override async Task OnInitializedAsync()
    {
        await LoadDataAsync(async () =>
        {
            Items = await FetchItems();
        });
    }

    protected async Task OnDelete(T item)
    {
        await LoadDataAsync(async () =>
        {
            await DeleteItem(item);
            Items = await FetchItems();
            ShowSuccess("Item deleted.");
        });
    }
}

Then your actual page becomes super clean:

@page "/products"
@inherits CrudPageBase<Product>

@* just the markup, all logic lives in the base class *@

@code {
    protected override Task<List<Product>> FetchItems()
        => Http.GetFromJsonAsync<List<Product>>("api/products");

    protected override Task DeleteItem(Product item)
        => Http.DeleteAsync($"api/products/{item.Id}");
}

When to use it and when not to

Component inheritance is great for shared behavior like loading states, error handling, authentication checks, or CRUD patterns. But don’t go overboard with deep inheritance hierarchies — if you find yourself going more than two levels deep, you’re probably better off with composition (like the LoadingComponent approach from a previous post).

I usually keep it to one base class per “type” of page: PageBase for regular pages, FormPageBase for forms, and that’s about it.

Hope you liked the post! Feel free to contact me on any social media at @emimontesdeoca.

Resources