Summary
This proposal introduces a native-like reactive state management paradigm to Blazor using Signals (as in Angular Signals: https://angular.dev/guide/signals). It provides automatic dependency tracking to update components when data changes, eliminating the need to manually invoke StateHasChanged().
Motivation and goals
- Blazor does not automatically trigger UI updates for state changes
occurring outside of standard UI event handlers or input parameter updates (e.g., inside background threads, async tasks, or timer callbacks). Developers are forced to manually invoke StateHasChanged(), which leads to boilerplate, error-prone code, and
maintenance overhead.
- Provide a declarative mechanism inspired by modern frameworks (like
Angular Signals) that automatically intercepts reads and triggers precise, automated
component updates.
In scope
- Reactive primitives for "state management" (
Signal<T>, Computed<T> and friends)
- Track dependencies between those so we know when to re-render something and update the dependency graph
Out of scope
- I am not sure how SSR fits in here at this point in time. This proposal is strictly meant for Interactive Server/WASM.
- Angular has many many more "signal" like things (
resource or httpResource or even complete forms). While "nice", the initial batch of 3 to 5 (like Effect or Input) would be "good enough".
Risks / unknowns
Without deeply integrating into the Blazor Rendering Pipeline itself, we would need a wrapper component handling subscriptions etc. Therefore this could lead to a major driver of complexity inside the Blazor Renderer itself.
Examples
Here is an example taken from my blogpost where I dabbled with the idea. It utilizes a reactive wrapper as boundary. Ideally we wouldn't have that in the final design.
@page "/Signal"
@implements IDisposable
<h3>Signal use</h3>
<Reactive>
<p>Elapsed time: @elapsedMilliseconds.Value milliseconds</p>
<p>Computed elapsed time: @elapsedSeconds.Value seconds</p>
</Reactive>
@code {
private readonly Signal<int> elapsedMilliseconds = Signals.Signal(0);
private Computed<int> elapsedSeconds = null!;
private Timer? timer;
protected override void OnInitialized()
{
elapsedSeconds = Signals.Computed(() => elapsedMilliseconds.Value / 1000);
timer = new Timer(
_ => elapsedMilliseconds.Update(value => value + 1000),
null,
TimeSpan.Zero,
TimeSpan.FromSeconds(1));
}
public void Dispose()
{
timer?.Dispose();
}
}
Summary
This proposal introduces a native-like reactive state management paradigm to Blazor using Signals (as in Angular Signals: https://angular.dev/guide/signals). It provides automatic dependency tracking to update components when data changes, eliminating the need to manually invoke
StateHasChanged().Motivation and goals
occurring outside of standard UI event handlers or input parameter updates (e.g., inside background threads, async tasks, or timer callbacks). Developers are forced to manually invoke StateHasChanged(), which leads to boilerplate, error-prone code, and
maintenance overhead.
Angular Signals) that automatically intercepts reads and triggers precise, automated
component updates.
In scope
Signal<T>,Computed<T>and friends)Out of scope
resourceorhttpResourceor even complete forms). While "nice", the initial batch of 3 to 5 (likeEffectorInput) would be "good enough".Risks / unknowns
Without deeply integrating into the Blazor Rendering Pipeline itself, we would need a wrapper component handling subscriptions etc. Therefore this could lead to a major driver of complexity inside the Blazor Renderer itself.
Examples
Here is an example taken from my blogpost where I dabbled with the idea. It utilizes a reactive wrapper as boundary. Ideally we wouldn't have that in the final design.