DKX.Signals.Blazor
1.0.0-rc.10
Prefix Reserved
See the version list below for details.
dotnet add package DKX.Signals.Blazor --version 1.0.0-rc.10
NuGet\Install-Package DKX.Signals.Blazor -Version 1.0.0-rc.10
<PackageReference Include="DKX.Signals.Blazor" Version="1.0.0-rc.10" />
<PackageVersion Include="DKX.Signals.Blazor" Version="1.0.0-rc.10" />
<PackageReference Include="DKX.Signals.Blazor" />
paket add DKX.Signals.Blazor --version 1.0.0-rc.10
#r "nuget: DKX.Signals.Blazor, 1.0.0-rc.10"
#addin nuget:?package=DKX.Signals.Blazor&version=1.0.0-rc.10&prerelease
#tool nuget:?package=DKX.Signals.Blazor&version=1.0.0-rc.10&prerelease
DKX.Signals.Blazor 
<a href="https://www.buymeacoffee.com/david_kudera" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-violet.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
Integration of DKX.Signals with Blazor.
Installation
dotnet add package DKX.Signals
dotnet add package DKX.Signals.Blazor
dotnet add package DKX.Signals.Blazor.SourceGenerators
Before continuing, please read the README of the main project.
Example
@inherits DKX.Signals.Blazor.SignalsComponentBase
<p>Current count: @_counter.Get()</p>
<button @onclick="IncrementCount">Click me</button>
And the code-behind class:
using DKX.Signals;
using DKX.Signals.Blazor;
using DKX.Signals.Blazor.Attributes;
public sealed partial class Counter : SignalsComponentBase
{
[Watch]
private readonly IWritableSignal<int> _counter;
public Counter()
{
_counter = Scope.Signal(0);
}
private void IncrementCount()
{
_counter.Update(static x => x + 1);
}
}
Limitations
Source Generators
Read more here: https://github.com/dotnet/roslyn/issues/57239
Because source generators can't access code generated by other source generators, we have to use the code-behind approach while also
specifying the @inherits
directive in the Razor file and in the code-behind class. We use this as a marker to find all components in our
source generator.
Error BL0007
Read more here: https://github.com/dotnet/aspnetcore/issues/51372
This error can be safely ignored by creating .editorconfig
file in the root of your project with the following content:
[*.cs]
dotnet_diagnostic.BL0007.severity = none # Component parameter '*' should be auto property
JetBrains Rider and [EditorBrowsable]
Read more here: https://youtrack.jetbrains.com/issue/RSRP-330352/EditorBrowsable-is-ignored-by-default
There is some generated code that should not be visible in the editor. This is done by using the EditorBrowsable
attribute.
To hide this code in JetBrains Rider, see this comment in the issue above: https://youtrack.jetbrains.com/issue/RSRP-330352/EditorBrowsable-is-ignored-by-default#focus=Comments-27-6905957.0-0
Sealed components
Components that want to use the SignalsComponentBase
class must be sealed
.
SignalsComponentBase
SignalsComponentBase
is a base class for Blazor components that use DKX.Signals. It provides the following features:
- Dispose pattern
DisposeRequested
property with cancellation token- Disables all re-renders by Blazor and only allows re-renders when some signal is changed
- Default
Scope
property - Factory methods for creating signals
- Support for turning Blazor input parameters into signals
- Automatically disposing of effects
Scope
Each component has its own scope accessible with the Scope
property. The scope is created when the component is instantiated.
You don't have to use this scope, but it's there if you need it.
[Watch]
attribute
The [Watch]
attribute is used to mark a signal that should be watched for changes. When the signal changes, the component will re-render.
Changes to any other signal will not cause the component to re-render.
This attribute can be used on any field or property of these types:
ISignal<T>
IWritableSignal<T>
IResource<T>
Inputs
Any input parameter of the component can be turned into a signal. All you have to do is add the Input
attribute to the parameter and
mark it as public
and partial
.
The source generator will generate a new private
property with name Input{ParameterName}
. This property is a signal that will be updated
when the parameter changes.
Of course attributes [Input]
and [Watch]
can be used together.
using DKX.Signals;
using DKX.Signals.Blazor;
using DKX.Signals.Blazor.Attributes;
public sealed partial class Counter : SignalsComponentBase
{
[Watch]
private readonly IWritableSignal<int> _counter;
[Watch]
private readonly ISignal<int> _result;
public Counter()
{
_counter = Scope.Signal(0);
// InputMultiplier is automatically generated signal based on the Multiplier parameter
_result = Scope.Computed(new { _counter, InputMultiplier }, static deps => deps._counter.Get() * deps.InputMultiplier.Get());
}
[Parameter, Input]
public partial int Multiplier { get; set; } = 1;
private void IncrementCount()
{
_counter.Update(static x => x + 1);
}
}
Effects
Effects in Blazor works a bit differently than in the main project:
- Effects are automatically disposed of when the component is disposed.
- You can't create effects in the constructor. You have to create them in the
OnInitialized
method or later.
AsyncEffect
There is a special type of effect that is accessible only in components. It allows you to use async methods in the effect. That is useful when you want to, for example, fire an event when a signal changes.
public sealed partial class Counter : SignalsComponentBase
{
[Watch]
private readonly IWritableSignal<int> _counter;
public Counter()
{
_counter = Scope.Signal(0);
}
[Parameter]
public EventCallback<int> OnCountChanged { get; set; }
protected override void OnInitialized()
{
base.OnInitialized();
// Effect is automatically disposed of and can't be created in the constructor
Scope.AsyncEffect(
new { _counter },
async deps =>
{
await OnCountChanged.InvokeAsync(deps._counter.Get());
}
);
}
private void IncrementCount()
{
_counter.Update(x => x + 1);
}
}
Resources
Resource in Blazor have two differences compared to the main project:
- They are automatically disposed of when the component is disposed.
- They stay in idle state when created in the constructor until the component is initialized.
Store
Store is most useful when you want to share signals between components. You can use [Parameter]
, [CascadingParameter]
or any other way to
pass the store to the component.
using DKX.Signals;
using DKX.Signals.Blazor;
using DKX.Signals.Blazor.Attributes;
public sealed class MyStore : IStore
{
public IScope Scope { get; } = ScopeBuilder.Default.Create();
public readonly ISignal<int> Counter;
public MyStore()
{
Counter = Scope.Signal(0);
}
public void Increment()
{
Counter.Update(static x => x + 1);
}
}
public sealed partial class GlobalCounter : SignalsComponentBase
{
[CascadingParameter]
public required MyStore Store { get; set; }
// Expose the store signal to the component
[Watch] // <-- This attribute is required to watch re-render GlobalCounter when Store.Counter changes
public ISignal<int> Counter => Store.Counter;
private void IncrementCount()
{
Store.Increment();
}
}
Customize component scope
It's possible to provide customized ScopeBuilder
to the component. This is useful when you want to for example enable logging.
public sealed partial class Counter : SignalsComponentBase
{
// ...
// Or register the prepared custom ScopeBuilder in DI and use it here
public Counter(ILoggerFactory loggerFactory)
: base(ScopeBuilder.Default.WithLoggerFactory(loggerFactory))
{
}
// ...
}
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- DKX.Signals (>= 1.0.0-rc.10)
- Microsoft.AspNetCore.Components (>= 9.0.4)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
1.0.0-rc.11 | 116 | 5/8/2025 |
1.0.0-rc.10 | 116 | 5/8/2025 |
1.0.0-rc.9 | 112 | 5/7/2025 |
1.0.0-rc.8 | 113 | 5/7/2025 |
1.0.0-rc.7 | 112 | 5/6/2025 |
1.0.0-rc.6 | 115 | 5/4/2025 |
1.0.0-rc.5 | 39 | 5/3/2025 |
1.0.0-rc.4 | 50 | 5/3/2025 |
1.0.0-rc.3 | 44 | 5/3/2025 |
1.0.0-rc.2 | 75 | 5/2/2025 |
1.0.0-rc.1 | 114 | 5/1/2025 |