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.
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.
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; } }