Sven Bardos
Sven Bardos

Reputation: 872

RX Best Practice: Select with side-effect or use subscribe?

I convert events (Paging events from buttons) to IObservable and receive the result from a service asynchronously.

As a side effect I have to update a child view. I have two solutions:

  1. Side effect in the select:

        //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.
    
  2. Do the side effect in the subsribe:

        //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 are working. Which solution should I choose and why?

thx.

Because there is question why this produces 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;
            }

        });

What it actually does is sending a search request to a DB when the search string changes and get paged results OR get another page with the same search string when PageChangedEvent has been received. Does the .Merge(...) a subscription?

Upvotes: 4

Views: 1693

Answers (2)

Jim Jeffries
Jim Jeffries

Reputation: 10081

It's best practice for select and where to be pure functions (side effect free). It shows the intent much more clearly.

Also, as observables are composable and you might want to use the stream of data again elsewhere, it is best to make the side effects as explicit of possible.

Upvotes: 1

NeddySpaghetti
NeddySpaghetti

Reputation: 13495

I'd go with your second solution. It is more flexible as different subscribers can choose to have different side effects. It's also separates the concerns, the Rx query generates the data and the subscriber uses the data as he pleases.

Upvotes: 5

Related Questions