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

readonly Subject<Unit> _parametersSet = new ();

public override async Task SetParametersAsync(ParameterView parameters)
{
    await base.SetParametersAsync(parameters);
    _parametersSet.OnNext(Unit.Default);
}
Observable sequence from SetParametersAsync() - typically implemented in a base class

This will allow us to observe change on any combination of parameters using C# Tuples and DistinctUntilChanged operator:

[Parameter] public string Language { get; set; }
[Parameter] public int DocumentId { get; set; }

protected override void OnInitialized()
{
    _parametersSet.Select(_ => (Language, DocumentId))
        .DistinctUntilChanged()
        .Do(_ => 
        {
            Console.WriteLine($"Either Language or DocumentId has changed.");
            Console.WriteLine($"New Values: '{Language}' - {DocumentId})");
        })
        .Subscribe();
}
observe changes to multiple properties

Or you can easily access previous values if you want:

_parametersSet.Select(_ => DocumentId)
    .DistinctUntilChanged()
    .Scan((previous: 0, value: 0), (previousChange, val) => (previousChange.value, val)) //start with 0
    .Subscribe(change =>
	{
	   Console.WriteLine("DocumentId: {change.value}, Previous Value: {change.previous}");				
    });
access previous value when parameter changes

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.

  var loadObservable = parameterObservable
     .Select(_ => Observable.FromAsync(LoadAsync))
     .Switch();
Select/Switch pattern

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

  1. Add a reload button that will trigger Load method. Make sure the Load call is cancelled when parameters changes or component is disposed.
  2. 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:

Blazor: An unopinionated library for toast notifications
GitHub - Liero/vNext.BlazorComponents.Toasts: A javascript free blazor library for toasts notificationsA javascript free blazor library for toasts notifications - GitHub - Liero/vNext.BlazorComponents.Toasts: A javascript free blazor library for toasts notificationsGitHubLieroI’ve created Toast Noti…
Advanced validation in Blazor using FluentValidation
You are probably familiar with the great FluentValidation validation library - defacto industry standard for any advanced validation scenarios in .NET. There has been a few attempt to integrate FluentValidation with Blazor Forms:Blazored.FluentValidation, Accelist.FluentValidation.Blazor, just to na…