Best RX Practice: Choose With Side Effect or Use Subscription?
I am converting events (paging events from buttons) to IObservable and receiving the result from the service asynchronously.
As a side effect, I need to update the child mode. I have two solutions:
-
Side effect when choosing:
//Converting events to a "stream" of search results. IObservable<SearchResult> resultsFromPageChange = Observable.FromEventPattern<EventArgs>(this, "PageChangedEvent") .Throttle(TimeSpan.FromMilliseconds(500)) .Select(async ev => { var input = new SearchResultInput() { PageSize = PageSize, PageIndex = PageIndex, SearchOptions = _currentSearchOptions, SearchText = SearchText.Value }; var result = await GetPagedComponents(input); //Side effect ActivateAttributesView(result.Components.FirstOrDefault()); return result; }).Switch(); //flatten and take most recent.
-
Do side effect on subscription:
//Converting events to a "stream" of search results. IObservable<SearchResult> resultsFromPageChange = Observable.FromEventPattern<EventArgs>(this, "PageChangedEvent") .Throttle(TimeSpan.FromMilliseconds(500)) .Select(async ev => { var input = new SearchResultInput() { PageSize = PageSize, PageIndex = PageIndex, SearchOptions = _currentSearchOptions, SearchText = SearchText.Value }; return await GetPagedComponents(input); }).Switch(); //flatten and take most recent. //Do side effect in the subscribe resultsFromPageChange.Subscribe(x => { if (x != null) ActivateAttributesView(x.Components.FirstOrDefault()); });
Both methods work. Which solution should I choose and why?
THX.
Because the question is why this creates values at all, here's the complete code:
//Converting events to a "stream" of search results.
IObservable<SearchResult> resultsFromPageChange = Observable.FromEventPattern<EventArgs>(this,
"PageChangedEvent")
.Throttle(TimeSpan.FromMilliseconds(500))
.Select(async ev =>
{
var input = new SearchResultInput()
{
PageSize = PageSize,
PageIndex = PageIndex,
SearchOptions = _currentSearchOptions,
SearchString = SearchString.Value
};
return await SearchComponentsPaged(input);
}).Switch(); //flatten and take most recent.
SearchString = new ReactiveProperty<string>(""); //Bound to TextBox.Text
//Create a "stream" of search results from a string given by user (SearchString)
SearchResult = SearchString
.SetValidateNotifyError(s => _componentDataService.ValidateSearchStringLength(s).Errors)
.Throttle(TimeSpan.FromMilliseconds(500))
.Select(async term =>
{
var input = new SearchResultInput()
{
PageSize = PageSize,
PageIndex = PageIndex,
SearchOptions = _currentSearchOptions,
SearchString = term
};
return await SearchComponentsPaged(input);
})
.Switch()
.Merge(resultsFromPageChange) //Merge the result of paging and text changing.
.ToReactiveProperty();
//Update Attributes view
SearchResult.Subscribe(searchResult =>
{
if (searchResult != null)
{
ActivateAttributesView(searchResult.Components.FirstOrDefault());
SearchResult.Value.TotalItemsCount = searchResult.TotalItemsCount;
}
});
This is actually sending a search query to the DB when the search string changes and getting page-by-page results OR getting another page with the same search string when the PageChangedEvent page is received. Does .Merge (...) include a subscription?
I would go with my second solution. It is more flexible as different subscribers can choose different side effects. It also decouples issues, the Rx request generates data and the subscriber uses the data as he wants.
This is the best practice for picking and where to be pure functions (side effect free). This shows intent much more clearly.
Also, since observables can be combined and you can reuse the data stream elsewhere, it's best to make the side effects explicit.