1

I'm building a Blazor client side app (WebAssembly). Is there a way to trigger an update of the current state on all pages currently displayed?

In my application I change language from the sidebar and I want that change to refresh the currently displayed components and pages. Reloading the page is not a good solution for me since the application is keeping a lot of state that I prefer to avoid persisting in local storage or similar.

I have made it work by explicitly hooking up an event to trigger StateHasChanged() in the page, but that requires me to do so on every page.

I've reproduced the issue with the default Blazor template to illustrate the behavior. The sidebar and page is using a very simple common state service:

public class StateService
{
    public int CurrentCount { get; set; }
}

in Program.cs:
builder.Services.AddScoped<StateService>();

enter image description here

FishySwede
  • 1,563
  • 1
  • 17
  • 24

1 Answers1

3

Use events.

See - How can I trigger/refresh my main .RAZOR page from all of its sub-components within that main .RAZOR page when an API call is complete?

public class StateService
{
    public event EventHandler? CounterChanged;

    private int _currentCount;
    public int CurrentCount
    {
        get => _currentCount;
        set
        {
            _currentCount = value;
            this.CounterChanged?.Invoke(this, EventArgs.Empty);
        }
    }
}

You can also implement CurrentCount with a private setter and then implement a setter method that triggers the event.

    public int CurrentCount {get; private set;}

    public void SetCount(int value)
    {
        this.CurrentCount = value;
        this.CounterChanged?.Invoke(this, EventArgs.Empty);
    }
//...
@code{
    [Inject] private StateService Service {get; set;} = default!;

    protected override void OnInitialized()
        => this.Service.CounterChanged += this.OnStateChanged;

    private void OnStateChanged(object? sender, EventArgs e)
       => this.InvokeAsync(StateHasChanged);

    public void Dispose()
        => this.Service.CounterChanged -= this.OnStateChanged; 
}

If you want to implement this in a lot of components, create a base component that implements the necessary functionality and then use inheritance.

using Microsoft.AspNetCore.Components;

public class MyComponentBase : ComponentBase, IDisposable
{
    [Inject] private StateService Service { get; set; } = default!;

    private bool _firstRender = true;

    // I do this here so if any of the normal lifecycle methods are overridden
    // without a call to base then everything works 
    public override Task SetParametersAsync(ParameterView parameters)
    {
        // ALWAYS do this first.
        // This sets the parameters to the new values before we do anything else
        parameters.SetParameterProperties(this);
        if (_firstRender)
            this.Service.CounterChanged += this.OnStateChanged;

        _firstRender = false;
        // Set the base with an Empty ParameterView
        // Why? we've already set the parameters in the first line
        return base.SetParametersAsync(ParameterView.Empty);
    }

    private void OnStateChanged(object? sender, EventArgs e)
    => this.InvokeAsync(StateHasChanged);

    public virtual void Dispose()
        => this.Service.CounterChanged -= this.OnStateChanged;
}
@page "/"
@inherits MyComponentBase

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>
//...
MrC aka Shaun Curtis
  • 19,075
  • 3
  • 13
  • 31
  • Yes, this does work. I was hoping to avoid having to implement this on every page in the project though. I'll use this approach if I can't find a less intrusive one. – FishySwede Feb 24 '23 at 11:04
  • 2
    See my updated answer using a base component and inheritance. – MrC aka Shaun Curtis Feb 24 '23 at 11:17
  • That's a really neat solution, with that update! That solves my "implement once, use everywhere" need. I'm going to make a "TranslatablePage" base that all translated pages can inherit. – FishySwede Feb 24 '23 at 11:31