State Management - Do you use the AppState pattern?
Last updated by Tiago Araújo [SSW] over 2 years ago.See historyThe AppState pattern is one of the simplest State Management patterns to implement with Blazor WebAssembly.
To start implementing the pattern, declare a class that describes the collection of fields that represents the state of a page, a form, or a model.
Here are some basic example state objects:
public class Counter
{
public int Counter { get; set; }
}
public class RegistrationForm
{
public Guid FormId { get; set; }
public string EmailAddress { get; set; }
public string GivenName { get; set; }
public string Surname { get; set; }
public string JobTitle { get; set; }
}
public class TimesheetEntry
{
public int Id { get; set; }
public int ClientId { get; set; }
public string ClientName { get; set; }
public int ProjectId { get; set; }
public string ProjectName { get; set; }
public decimal HourlyRate { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public string Notes { get; set; }
}
public class Timesheet
{
public int Id { get; set; }
public string UserName { get; set; }
public TimesheetEntry[] Entries { get; set; }
}
Typically, these state objects would be hydrated from user input or a request to the backend API. In order for us to use this state object, we first need to register it as an injectable service (in Program.cs
):
builder.Services.AddScoped<Counter>();
builder.Services.AddScoped<RegistrationForm>();
builder.Services.AddScoped<Timesheet>();
Once registered, we can use the @inject
directive to inject the object into a page or component:
@page "/counterWithState"
@* Inject our CounterState and use it in the view and/or code section *@
@inject Counter _state
<PageTitle>Counter</PageTitle>
@* we can reference the state object in the Razor markup *@
<p>Current count: @_state.Count</p>
@* Note: Due to user interaction, the page will refresh and show updated state value, even though we have not called StateHasChanged *@
<button type="button" @onclick="IncrementCount">Click me</button>
<button type="button" @onclick="Reset">Reset</button>
@code {
private void IncrementCount()
{
// we can modify the state object in the @code section
++_state.Count;
}
private void Reset()
{
_state.Count = 0;
}
}
Alternatively if we are using code-behind (separate .razor and .razor.cs files), then we can use the [Inject]
attribute to inject the object as a parameter for the code-behind class.
Note: Constructor based injection is not supported for Blazor code-behind. Only Parameter based injection is supported.
public partial class Counter : ComponentBase
{
[Inject]
public Counter State { get; set; }
private void IncrementCount()
{
++_state.Count;
}
private void Reset()
{
_state.Count = 0;
}
}
Drawbacks of basic AppState pattern
❌ We are unable to react to state changes made to the state object by other components
❌ We can modify the state but the page will not refresh to reflect the change
❌ We need to call StateHasChanged()
manually when we modify the state
Benefits of basic AppState pattern
✅ Implementation is trivial - register, inject, consume
✅ Works for very basic scenarios - especially if there are basic user interactions and basic state mutations directly on the @code
(aka ViewModel
) section