Ditch Your Angular 1.x Watchers! (Follow Up)

Note:
This is a follow up to Ditch Your Angular 1.x Watchers! Quick-Swap Them for Observables

It's been 2 months since I wrote about swapping $watch for observeOnScope, so I thought I'd post a follow up! In that 2 months we have finally found the time to educate the team on Observables and actually implement these swaps in our production application!

In doing so, we ran across a scenario that I hadn't covered in the "Quick-Swap" article.


The Problem

$watchGroup.

We had a couple of places where we had the need to detect changes on several objects, then run one function if any of them changed (think auto-sorting across multiple list of different types of objects).

It looks something like this:

Controller1.$inject = ['$scope'];  
function Controller1($scope) {  
    $scope.property1 = {};
    $scope.property2 = {};
    $scope.property3 = {};

    $scope.$watchGroup(['property1', 'property2', 'property3'], function(newValue, oldValue) {

        if (newValue && oldValue) {
            //Do Something        
        }
    }, true);
}

Since observeOnScope only takes one attribute (or function), we have a problem.

If you don't need to know which value actually changed, you could combine the object properties with a function and pass it into observeOnScope. However, in our case, our "on-change" function requires the most up-to-date data from all of the objects. With this method, we would have had to do change detection on our change detection in order to figure out which one of the objects actually triggered the change.

You also run into this same problem if you try to use Observable.merge which creates a single Observable out of multiple.

You could also break the "on-change" function up and split each object into its own Observable and Subscription. However, in our case, that would result in a lot of repeated code, which we didn't want to do.


The Solution

Enter Observable.combineLatest! This function accepts any number of Observables. Then when something changes, it emits a list of the most recent values for each Observable in the order that it was given to the combineLatest function.

It looks like this:

Controller1.$inject = ['$scope', 'rx', 'observeOnScope'];  
function Controller1($scope, rx, observeOnScope) {  
    $scope.property1 = {};
    $scope.property2 = {};
    $scope.property3 = {};

    rx.Observable.combineLatest(
            observeOnScope($scope, 'property1', true),
            observeOnScope($scope, 'property2', true),
            observeOnScope($scope, 'property3', true)
        )
        .subscribe(function(changes) {

          /*  changes will look like this:
             [
                  {newValue: {...}, oldValue:{...}},
                  {newValue: {...}, oldValue:{...}},
                  {newValue: {...}, oldValue:{...}}
             ]
         */
        });
}

As you can see, we now have a predictable object that gives us the new value for each "watched" property every time any of them change.


Final Thoughts

If you're wondering if there's a thing that rxjs can do, the answer is most likely "yes". There are a TON of helper functions and built in functionality. The hard part is finding it.

Thankfully, the documentation is continually getting better. Things like RxMarbles are also attempting to help with this.

In fact, here is the RxMarble for combineLatest!


comments powered by Disqus