Reactive View Model

User interactions can be viewed as streams of events. Those events can be combined in complex and time-sensitive ways. A reactive view model combines event streams to construct new properties.

Structure

A property change is an event that occurs over time. These events are collected into a stream. Various operations are performed on streams to throttle them to a certain maximum frequency, filter them to include only matching occurrences, and combine them to produce desired results.

IObservable

This pattern requires assistance from a library such as ReactiveUI. This library is built on top of the Reactive Extensions for C#, which defines the IObservable interface. This interface models a sequence of events as a collection. This event stream can be operated on to produce other streams.

For example, we can start with a property, and turn it into a stream of property changed events. Take the value of the property at each point, and it becomes a stream of strings. Throttle the stream so that it waits for a half-second pause between values.

IObservable<string> searchTerms = this
    .ObservableForProperty(x => x.SearchTerm)
    .Value()
    .Throttle(TimeSpan.FromSeconds(0.5));

Then run the search service each time we get a new search term. This produces a stream of search results.

IObservable<SearchResult> searchResults = searchTerms
    .SelectMany(searchTerm => _searchService.SearchAsync(searchTerm));

Combine the search terms with the search results, and filter to only the results matching the latest term. This eliminates results that arrive out of order.

IObservable<List<string>> latestMatches = searchTerms
    .CombineLatest(searchResults,
        (searchTerm, searchResult) =>
            searchResult.SearchTerm != searchTerm
                ? null
                : searchResult.Matches)
    .Where(matches => matches != null);

Finally, turn this stream back into a property so that it can be data bound.

_Matches = latestMatches
    .ToProperty(this, x => x.Matches);

Where:

private ObservableAsPropertyHelper<List<string>> _Matches;

public List<string> Matches
{
    get { return _Matches.Value; }
}