In Blazor, how do I make changes to an injected object be reflected in all components? (AKA reactivity)

Viewed 1054

In a Blazor WebAssembly site, suppose I have a class

    public class State : IState
    {
        public bool ShowEasterEggs { get; set; } = false;
    }

And I properly register it in my client's Program.cs: builder.Services.AddSingleton<IState, State>();

And I properly inject this into a component:

@inject IState State
@if (State.ShowEasterEggs)
{
  <span>EASTER EGGS SHOWN</span>
}

And in some other component, I enable a change to it:

@inject IState State
<input type="checkbox" @bind="State.ShowEasterEggs"/>Show Easter Eggs

I expect that a change to State.ShowEasterEggs from the second component would be detected and any instance of the first component would update automatically. But I find that I need to do some other interaction--changing other values in the first component's parent, to see the change in the first component's instance.

So how does this work, how should it work, and how can I get changes to injected objects to cause an update?

2 Answers

There are various ways to do it. The following one is sort of a service that implements the State pattern and the Notifier pattern:

State.cs

 public interface IState
{
    event Action Notify;
    bool ShowEasterEggs { get; set; }
}
public class State : IState
{
    public event Action Notify;

    bool showEggs = false;
    public bool ShowEasterEggs
    {
        get => showEggs;
        set
        {
            if (showEggs != value)
            {
                showEggs = value;

                if (Notify != null)
                {
                    Notify?.Invoke();
                }
            }
        }
    }

}

Child1.razor

 @inject IState State

 <input type="checkbox" @bind="State.ShowEasterEggs" />Show Easter Eggs
 <hr />


 @code {

 }

Child2.razor

 @inject IState State
 @implements IDisposable

@if (State.ShowEasterEggs)
{
    <span>EASTER EGGS SHOWN</span>
}

<hr />


@code {
   protected override void OnInitialized()
  {
       State.Notify += OnNotify;
  }

 public void OnNotify()
 {
    InvokeAsync(() =>
    {
        StateHasChanged();
    });
}

public void Dispose()
{
    State.Notify -= OnNotify;
}
}

Index.razor

 @page "/"

 <Child1></Child1>
 <Child2></Child2>



 @code { }

Program.cs

builder.Services.AddSingleton<IState, State>();

AS you've probably noticed, the State service store the state of the check box, and notify subscribers of changes to this state.

Hope this helps...

You need to use Cascading values and parameters

You would need to have a CascadingValue component that is parent of any component that will react to the changes.

CascadingValue will receive a property called Value that will hold the value you want to the children to see (State).

And you will get that property in your child components and pass an attribute CascadingParameter to a property with the same name you passed in CascadingValue's Value attribute.

e.g. from the docs

The parent

@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses

@inject IState State @* Injecting the State *@

<div class="container-fluid">
    <div class="row">
        <div class="col-sm-3">
            <NavMenu />
        </div>
        <div class="col-sm-9">
            <CascadingValue Value="State"> @* Passing State as a Cascading Value*@
                <div class="content px-4">
                    @Body
                </div>
            </CascadingValue>
        </div>
    </div>
</div>

Any child component

<input type="checkbox" @bind="State.ShowEasterEggs"/>Show Easter Eggs

@code {

    // Getting the Cascading Value as a parameter
    [CascadingParameter]
    protected State State { get; set; }

}
Related