DEV Community

Dominic Pascasio
Dominic Pascasio

Posted on • Updated on

Blazor WASM - Shared State Between Layout and Page

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).

    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.

    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:

    @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 method.
    • We implement IDisposable, so that we can unsubscribe from NamesService.StateChanged when the page is disposed.

    In our page, it is the same:

    @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
Microsoft Docs - Service Lifetime
Microsoft Docs - StateHasChanged
Microsoft Docs - State Management

Top comments (0)