Skip to content

Reactive Library WhenPropertyChanged

andyb1979 edited this page Sep 19, 2018 · 1 revision

WhenPropertyChanged usage

WhenPropertyChanged is an extension method in the SciChart.UI.Reactive.Observability namespace which provides a reactive stream to one or more properties.

Given a viewmodel defined as follows:

public enum SearchOptions
{
    AnOption,
    AnotherOption,
}

public class WhenPropertyChangedTests : ObservableObjectBase
{        
    public string SearchText
    {
        get => GetDynamicValue<string>();
        set => SetDynamicValue(value);
    }

    public SearchOptions SearchOptions
    {
        get => GetDynamicValue<SearchOptions>();
        set => SetDynamicValue(value);
    }
}

Properties may be observed using Reactive Extensions as follows

void Foo()
{
	// Create the ViewModel
	WhenPropertyChangedTests vm = new WhenPropertyChangedTests();

	// Observe properties
	var observable1 = vm.WhenPropertyChanged(x => x.SearchText);
	var observable2 = vm.WhenPropertyChanged(x => x.SearchOptions);

	// Subscribe 
	var disposable = observable1.Subscribe(t => Console.WriteLine($"Search Text = '{t}'"));

	// Set properties 
	vm.SearchText = "Hello"; // -> Should output 'Search Text = 'Hello'' to console
	vm.SearchText = "World"; // -> Should output 'Search Text = 'World'' to console

	disposable.Dispose();

	vm.SearchText = "Not observed"; // nothing happens 
}

Multiple properties may be observed using Observable.CombineLatest

void Foo()
{
    // Create the ViewModel
    WhenPropertyChangedTests vm = new WhenPropertyChangedTests();

    // Observe properties
    var observable1 = vm.WhenPropertyChanged(x => x.SearchText);
    var observable2 = vm.WhenPropertyChanged(x => x.SearchOptions);

    // Subscribe 
    var disposable = Observable.CombineLatest(observable1, observable2, Tuple.Create)
        .Subscribe(t => Console.WriteLine($"Search Text = '{t.Item1}', Options = '{t.Item2}'"));

    // Set properties 
    // 

    // -> Should output 'Search Text = 'Hello', Options = 'AnOption'' to console
    vm.SearchText = "Hello";
    // -> Should output 'Search Text = 'Hello', Options = 'AnotherOption'' to console 
    vm.SearchOptions = SearchOptions.AnotherOption;
    // -> Should output 'Search Text = 'World', Options = 'AnotherOption'' to console
    vm.SearchText = "World";

    disposable.Dispose();

    vm.SearchText = "Not observed"; // nothing happens 
}

DisposeWith(this)

The Extension method DisposeWith() ensures a reactive obsevable is disposed with a parent which implements ICompositeDisposable (including all viewmodel and trait types in SciChart.UI.Reactive)

void Foo()
{
    // Create the ViewModel
    WhenPropertyChangedTests vm = new WhenPropertyChangedTests();

    // Observe properties
    var observable1 = vm.WhenPropertyChanged(x => x.SearchText);
    var observable2 = vm.WhenPropertyChanged(x => x.SearchOptions);

    // Subscribe 
    var disposable = Observable.CombineLatest(observable1, observable2, Tuple.Create)
        .Subscribe(t => Console.WriteLine($"Search Text = '{t.Item1}', Options = '{t.Item2}'"));

    disposable.DisposeWith(vm);


    // Dipose the parent, disposes subscription above 
    vm.Dispose(); 

    vm.SearchText = "Not observed"; // nothing happens 
}

Worked Example / Searching with two properties

Imagine you have a class which provides searches to a remote server. You want to call a method SearchForResults which is an async call with multiple parameters when the parameters change in a ViewModel, which are bound to user controls. You want to prevent too many searches to the server and want nice neat observable code to do it.

Starting off with the definition of the search service like this:

public interface ISearchService
{
    Task<ResultViewModel> SearchForResults(string searchText, SearchOptions options);
}

public enum SearchOptions
{
    AnOption,
    AnotherOption,
}

public class ResultViewModel : ViewModelBase
{        
    public string ResultText
    {
        get => GetDynamicValue<string>();
        set => SetDynamicValue(value);
    }
}

We might create a ViewModel which observes properties like this:

public class WhenPropertyChangedTests : ObservableObjectBase
{        
    public WhenPropertyChangedTests(ISearchService searchProvider)
    {
        // TODO: The search 
    }

    public string SearchText
    {
        get => GetDynamicValue<string>();
        set => SetDynamicValue(value);
    }

    public SearchOptions SearchOptions
    {
        get => GetDynamicValue<SearchOptions>();
        set => SetDynamicValue(value);
    }

    public IEnumerable<ResultViewModel> SearchResults
    {
        get => GetDynamicValue<IEnumerable<ResultViewModel>>();
        set => SetDynamicValue(value);
    }
}

Adding the Property Observability

Now, the next part is to observe the properties SearchOptions and SearchText to call the search service and get results.

Update the constructor of the ViewModel as follows:

public class WhenPropertyChangedTests : ObservableObjectBase
{        
    public WhenPropertyChangedTests(ISearchService searchProvider, ISchedulerContext scheduler)
    {
        // When either property changes (creates a Tuple)
        this.WhenPropertiesChanged(x => x.SearchText, x => x.SearchOptions)

            // Throttle events within 500ms
            .Throttle(TimeSpan.FromMilliseconds(500), scheduler.Default)

            // Does the search 
            .Select(args => searchProvider.SearchForResults(args.Item1, args.Item2).ToObservable())
            .Switch()

            // Sets results on the ViewModel
            .ObserveOn(scheduler.Dispatcher)
            .Subscribe(results => SearchResults = results)
            .DisposeWith(this);
    }

    public string SearchText
    {
        get => GetDynamicValue<string>();
        set => SetDynamicValue(value);
    }

    public SearchOptions SearchOptions
    {
        get => GetDynamicValue<SearchOptions>();
        set => SetDynamicValue(value);
    }

    public IEnumerable<ResultViewModel> SearchResults
    {
        get => GetDynamicValue<IEnumerable<ResultViewModel>>();
        set => SetDynamicValue(value);
    }
}