Blazor - Take advantage of System.Reactive aka Observables - Part 2
In November 2020 I wrote an article about RX in Blazor: how to turn a property into Observable and perform async operations (e.g. loading) based on property values. In this blog I will show even simpler and more flexible way.
Before we start, I recommend you to have a look at the original article, so that you are familiar with the problems we are trying to solve.
Turning parameter(s) into Observable
Let's turn SetParametersAsync lifecycle method into observable
This will allow us to observe change on any combination of parameters using C# Tuples and DistinctUntilChanged operator:
Or you can easily access previous values if you want:
Select/Switch and TakeUntil(Disposed) patterns
Given that you have async Task LoadAsync(CancellationToken ct)
method, you should use take care of cancellation when parameter changes quicker that the Load method completes.
Or when the component is disposed:
readonly Subject<Unit> _disposed = new ();
void IDisposable.Dispose()
{
_disposed.OnNext(Unit.Default);
}
loadObservable
.TakeUntil(_disposed)
.Subscribe();
Put it all together
You can move SetParameterAsync
and Dispose
methods to a base class and expose Disposed and ParameterSet observables.
You can also create a reusable method that automatically applies DistinctUntilChanged and TakeUntil
public class CustomComponentBase : ComponentBase, IDisposable
{
private Subject<Unit> _parametersSet = new ();
private Subject<Unit> _disposed = new ();
public IObservable<Unit> Disposed => _disposed.AsObservable();
public IObservable<Unit> ParametersSet => _parametersSet.AsObservable();
/// <summary>
/// Turns a parameter property into IObservable using <see cref="ParametersSet"/> observable
///
/// It only emmits, when value is changed (DistinctUntilChanged)
/// The observable completes on <see cref="Dispose"/> (TakeUntil(<see cref="Disposed")/>
/// </summary>
/// <param name="parameterSelector">Parameter Property to observe</param>
/// <example>
/// <![CDATA[
/// this.ObserveParameter(() => Id)
/// .Select((id, ct) => LoadAsync(id, ct)
/// .Switch()
/// .Subscribe()
/// ]]>
/// </example>
public IObservable<T> ObserveParameter<T>(Func<T> parameterSelector)
{
return ParametersSet.Select(_ => parameterSelector())
.DistinctUntilChanged()
.TakeUntil(_disposed);
}
public override async Task SetParametersAsync(ParameterView parameters)
{
await base.SetParametersAsync(parameters);
_parametersSet.OnNext(Unit.Default);
}
void IDisposable.Dispose()
{
_disposed.OnNext(Unit.Default);
}
}
Check this REPL for a live demo: https://blazorrepl.telerik.com/QQullYbo21fLFclq27
Homework
- Add a reload button that will trigger Load method. Make sure the Load call is cancelled when parameters changes or component is disposed.
- Create a reusable class called
ReactiveLoader
that takes an observable as an input, calls and cancels specified async method, allows manual reloading and exposes IsLoading property.
Check out also: