Search code examples
angularjsangularjs-scopeangularjs-service

Setup a $watch to a return value of a service's function in AngularJS


TL;DR How can I setup, in a controller, a $watch to a DI'ed service's function return value?

My app has a service in order to make it easy to share data between controllers. I'm trying to setup a $watch to one of those variables from a controller. However, I wanted to access through the service's getter for that particular variable.

This is the code in the controller

    //this is the controller
    var vm = this;
    vm.isValidPhase = false;

    activate();

    ////////////////

    function activate() {
        vm.isValidPhase = userProgressService.getCurrentPhaseValidity();

        $scope.$watch('userProgressService.getCurrentPhaseValidity()',
                     function(newValue) {
            vm.isValidPhase = newValue;
        });
    }

And in the userProgressService I have:

   //this is the service
   var current = {
        phaseNumber : 0,
        phaseValidity : false,
        phase   : {},
        userInput   : {}
    };

    // ... ... ...

    var exports = {
        getCurrentPhaseData  : getCurrentPhaseData,
        getCurrentPhaseValidity : getCurrentPhaseValidity,
        setCurrentPhaseValidity : setCurrentPhaseValidity,
        getExistingUserInput : getExistingUserInput
    };

    return exports;

    // ... ... ...

    function getCurrentPhaseValidity() {
        return current.phaseValidity;
    }

    // ... ... ...

In my unit tests to the controller using mocha, bard, chai and sinon, I'm doing:

describe('initial state', function () {
    it('should be an invalid phase', function () {
        expect(userProgressService.getCurrentPhaseValidity(),
               'in service function').to.be.false;
        expect(controller.isValidPhase,
               'in controller scope').to.be.false;
    });
});

And I'm getting an error on the second assertion:

AssertionError: in controller scope: expected undefined to be false

Now, from playing with commenting and uncommenting, I've noticed that the problem comes from the $watch expression in the controller. But I don't know what is the problem with it... What am I doing wrong?


Solution

  • As you can see in the comments @JBNizet pointed out that the $watch expression is wrong, instead, I should be watching the function itself, since the service is not defined in the $scope. However, he also made me realize that, at least for this case, I didn't need to setup that $watch. In his own words

    [...] expose the function of the service in the scope (i.e. $scope.getCurrentPhaseValidity = userProgressService.getCurrentPhaseValidity;), and use <button ng-show="getCurrentPhaseValidity()">

    I did as he told and changed the controller scope to

    //... controller ...
    vm.isValidPhase = userProgressService.getCurrentPhaseValidity;
    

    Now I can use ng-show and alike with isValidPhase in the HTML under the controller.

    As expected, it worked and fitted the purpose for which I wanted to watch the variable, which was to activate/deactivate a button based on its value.

    For what I can think of, this solution covers much of whatever related problems you'd have with this.