Blazor WASM - Shared State Between Layout and Page
Tags: dotnet, aspnet, blazor, webassembly
Published: 2022-09-27 17:29:46

We can easily share state data between components (e.g. a page and a layout), but changing the data from either of them does not automatically re-renders the other if necessary.

1. Create a class that contains the data. The class should have an event which we will use to notify the subscribers (our components).

    ```cs
    using System.Collections.ObjectModel;

    public class NamesService{
        private readonly List<string> _names = new List<string>();
        public ReadOnlyCollection<string> Names { 
            get { return _names.AsReadOnly(); } 
        } 
        //the event
        public event Action? StateChanged;
        public void AddName(string name) {
            _names.Add(name); 
            StateChanged?.Invoke(); //raise the event
        }
    }
    ```
    We are using event. Dependents (blazor components) of this class should subscribe to `StateChanged` event.

    Note that we are returning a readonly collection to avoid adding data directly, dependents should use `AddName` instead to raise the event.

2. Register the state container in DI as a Singleton. In your `Program.cs`.

    ```cs
    builder.Services.AddSingleton<NamesService>();
    ```
    Since we do not want to create a new instance of `NamesService` everytime we switch the page, we will make it a singleton. 

3. Subscribe to `StateChanged` event from our component. In our layout for example:
    ```cs
    @inherits LayoutComponentBase
    @implements IDisposable
    @inject NamesService NamesService
    <!-- some code goes here -->
    <span>@NamesState.Names.Count</span>
    <button @onclick="Add">Add</button>
    <!-- some code goes here -->
    @Body
    <!-- some code goes here -->
    @code {
        protected override void OnInitialized() {
            NamesService.StateChanged += this.StateHasChanged; // subscribe
        }
        protected void Add() {
            NamesService.Add("from layout: " + DateTime.UtcNow.ToString()); // add random data
        }
        public void Dispose() {
            NamesService.StateChanged -= this.StateHasChanged; // unsubscribe
        }
    }
    ```
    Things to note:
    - We use @inject to get the instance of the `NamesService`.
    - When the component is initialized (`OnInitialized`), we subscribe to `NamesService.StateChanged` with the component's [`StateHasChanged`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.componentbase.statehaschanged?view=aspnetcore-6.0) method.
    - We implement `IDisposable`, so that we can unsubscribe from `NamesService.StateChanged` when the page is disposed.

    In our page, it is the same:
    ```cs
    @page "/test"
@implements IDisposable
    @inject NamesService NamesService
    <ul>@foreach(var name in NamesState.Names)
    { <li>@name</li> }
    </ul>
    <button @onclick="Add">Add</button>
    @code {
        protected override void OnInitialized() {
            NamesService.StateChanged += this.StateHasChanged; // subscribe
        }
        protected void Add() {
            NamesService.Add("from page: " + DateTime.UtcNow.ToString()); // add random data
        }
        public void Dispose() {
            NamesService.StateChanged -= this.StateHasChanged; // unsubscribe
        }
    }
    ```
To summarize, when the `NamesService.Add()` is called, it raises the event `NamesService.StateChanged`, all subscribers of the event will be notified, in our example, `StateHasChanged` of our components will be called.

Our components can now add data and the changes should reflect to all subscribing components.

**Resources**

[Microsoft Docs - Event](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/event)
[Microsoft Docs - Service Lifetime](https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/dependency-injection?view=aspnetcore-6.0#service-lifetime)
[Microsoft Docs - StateHasChanged](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.componentbase.statehaschanged?view=aspnetcore-6.0)
[Microsoft Docs - State Management](https://learn.microsoft.com/en-us/aspnet/core/blazor/state-management?view=aspnetcore-6.0&pivots=webassembly)