You used inheritance. So, it might seem that you manipulate the same variable because they have the name, but in fact, they are all unique instances with unique values.
If you want to exchange a value between different components, there are three ways to do it. This blog post describe them. Based on your description, I'd recommend an AppState approach. I've written an answer here.
I've copied the relevant parts for my previous answer and add some new thoughts.
Let's start by defining the AppState
. This class uses the .NET events or delegates to raise a notification when a value has changed.
public class AppState
{
private Int32 _clickCounter = 0;
public Int32 ClickCounter
{
get => _clickCounter;
set
{
_clickCounter = value;
AppStateChanged?.Invoke(nameof(ClickCounter), this);
}
}
public event AppStateChangedHandler AppStateChanged;
}
The handler public delegate void AppStateChangedHandler(String propertyName, AppState state);
. The change handler includes the object itself and the propertyName
, that has changed. This can be used by components subscribing to the event to filter if the change is relevant.
The AppState is added to the dependency injection system. For Blazor WASM, it should be a singleton dependency. For Blazor Server, though, it should be a scoped one.
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
...
builder.Services.AddSingleton(new AppState());
...
await builder.Build().RunAsync();
}
The Counter
component from the default template should be an example of using the AppState to propagate a change. This component is changed slightly.
@page "/Counter"
@inject AppState appState
<h1>Counter</h1>
<p>Current count: @appState.ClickCounter</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private void IncrementCount()
{
appState.ClickCounter++;
}
}
Instead of incrementing (and reading) a local variable, the corresponding property from the AppState is used.
To simplify the subscription to the change event and reduce the boilerplate in each component, we create an abstract base called AppStateAwareComponentBase
. This should be a normal .cs file.
public abstract class AppStateAwareComponentBase : ComponentBase, IDisposable
{
[Inject]
protected AppState AppState { get; private set; }
protected override void OnInitialized()
{
base.OnInitialized();
AppState.AppStateChanged += AppStateChanged;
}
protected abstract Boolean HandleAppStateChanged(String propertyName, AppState state);
private async void AppStateChanged(String propertyName, AppState state)
{
Boolean changesOccured = HandleAppStateChanged(propertyName, state);
if (changesOccured == true)
{
await InvokeAsync(StateHasChanged);
}
}
public void Dispose()
{
AppState.AppStateChanged -= AppStateChanged;
}
}
This class is subscribed to change event but doesn't know how to handle it exactly. Hence, it has an abstract method called HandleAppStateChanged
that need to be implemented in the concrete classes later. This class determines if a layout change is a subsequent result. The child calls returns true
if a layout update is needed. The parent class (AppStateAwareComponentBase
) initiate an layout update via invoking InvokeAsync(StateHasChanged)
.
If The AppState
is injected via [Inject]
and the implementation of IDisposable
guarantee that the subscription to the delegate is removed when the component is removed from the tree.
I've created a CounterHeader
component, as a class inherits from AppStateAwareComponentBase
@inherits AppStateAwareComponentBase
<span>Current App Global Counter: @_counter </span>
@code{
private Int32 _counter = 0;
protected override Boolean HandleAppStateChanged(String propertyName, AppState state)
{
if(propertyName != nameof(AppState.ClickCounter)) {
return false;
}
_counter = state.ClickCounter;
return true;
}
}
Here is a link to the corresponding repository.
Additional Thoughts
This is a fundamental version of an "AppState". However, there are frameworks like Fluxor for all these state transitions, or you could use a more decoupled way like a message bus for components, called ComponentBus.
There can be multiple "states" per application, like ShoopingCardState
, UserState
etc., to split it into different smaller parts.