Samuel Carswell
Samuel Carswell

Reputation: 128

Blazor, updating shared state in OnInitializedAsync with awaited object

I have the following code in a Blazor component:

    protected override async Task OnInitializedAsync()
    {
        appBarState.Title = "Analyses";
        project = await ApiService.GetFromJsonAsync<ProjectDTO>($"api/Projects/{ProjId}/dto");
        // TODO: Not working
        appBarState.Title = $"Analyses for project {project?.Name}";
        Console.WriteLine($"Analyses for project {project?.Name}");
    }

The intention is to set the top app bar with a default title, call the API to get more information about the object, and update the app bar with more accurate information using the shared AppBarState class below.

public class AppBarState
{
    public string Title { get; set; }
}

The app bar title is set to Analyses, the API is called and the correct value is returned. The Console.WriteLine line for $"Analyses for project {project?.Name}" works correctly, however trying to update the shared state with this value is not successful. The app bar title remains as Analyses, not even Analyses for project .

I'm assuming the issue is with updating shared state after an await call as trying to update the app bar state after the await call with any value (plain string without referencing project) fails to update.

Upvotes: 0

Views: 1586

Answers (2)

MrC aka Shaun Curtis
MrC aka Shaun Curtis

Reputation: 30340

I'm making the assumption that you are displaying the title in a different component or in the layout.

Add an event and a setter to AppBarState and register it as a Scoped DI service:

public class AppBarState
{
    public event EventHandler? TitleChanged;
    
    public string? Title { get; private set; }

    public void SetTitle(string title)
    {
        this.Title = title;
        this.NotifyTitleChanged();
    }

    public void NotifyTitleChanged()
        => this.TitleChanged?.Invoke(this, EventArgs.Empty);
}

Program:

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddScoped<AppBarState>();

Here's a component to display the title:

@inject AppBarState appBarState
@implements IDisposable

<h3>@this.appBarState.Title</h3>

@code {
    protected override void OnInitialized()
    => appBarState.TitleChanged += this.OnTitleChanged;

    // Kick off a re-redner event on the component
    private void OnTitleChanged(object? sender, EventArgs e)
    => this.InvokeAsync(this.StateHasChanged);

    public void Dispose()
     => appBarState.TitleChanged -= this.OnTitleChanged;
}

Here's a test page to show it working:

@page "/"

@inject AppBarState appBarState;

<h1>Hello</h1>

<TitleBar />

@code {

    protected override async Task OnInitializedAsync()
    {
        appBarState.SetTitle("Loading");
        // Emulate an async data get
        await Task.Delay(2000);
        appBarState.SetTitle($"Loaded at {DateTime.Now.ToLongTimeString()} ");
    }
}

Upvotes: 1

Wajeeh Hasan
Wajeeh Hasan

Reputation: 197

You need to call

StateHasChanged()

after updating the value of title

Upvotes: 1

Related Questions