BeetleJuice
BeetleJuice

Reputation: 40886

Angular: in Link, $watch(ctrl.value) does not work but $watch(function(){return ctrl.value}) does. why?

I have been reading other people's answers as to how to use $watch properly in AngularJS (1.4) but I still don't get why my code does not work. I don't understand how to watch a model value from within the directive's link function. Setting scope.$watch(controller.value) is only triggered before the value is bound to the controller (value still undefined), and later it is not triggered when the value changes. On the other hand, scope.$watch(function(){return controller.value}) works. Why is that?

In my example, I have an age value on the controller userCtrl. I want to add class btn-primary or btn-success to a button based on the value of controller.age: below is the directive:

app.directive('myDirective',function(){
  return {
    restrict: 'A',
    templateUrl:'my-directive.html',
    bindToController: true,
    controller: 'userController',
    controllerAs: 'userCtrl',
    scope: {user:'='}, //example: {name:'John',age:9}
    link: function(scope,element,attrs){
      var vm = scope.userCtrl;
      var el_button = element.find('button');

      //WHY IS $WATCH NOT GETTING CALLED WHEN THE AGE CHANGES?
      //if we change vm.user.age to function(){return vm.user.age} things work
      //but why is that any different?
      scope.$watch(vm.user.age,function(newVal,oldVal){
        console.log('age has changed to ' + newVal);
        if(newVal > 9){
          el_button.removeClass('btn-primary');
          el_button.addClass('btn-success');
        }else{
          el_button.removeClass('btn-success');
          el_button.addClass('btn-primary');
        }
      });
    }
  }
});

Demo on Plunker: http://plnkr.co/edit/nFjiCzVhVJkUGY5AEM1C?p=preview

Upvotes: 1

Views: 737

Answers (1)

PSL
PSL

Reputation: 123739

That is because the value returned by vm.user.age at that point of time is considered as expression (or property here) to be evaluated against scope. If at that point of time say value is 12 then it will set a watch on property 12 on the scope, which is obviously not what you expect.

You should give either a string representation (scope.$watch('userCtrl.user.age', function(newVal...) of the expression that can be evaluated against the scope or function that returns the value. When you provide userCtrl.user.age to be watched, angular will wrap the expression in a getter function with the help of $parse service, to say something like (just as example the getter function will be more complicated to tackle null checks etc..):

function(){
  return $scope.userCtrl.user.age;
}

and that will be evaluated during the digest cycle for change detection. So providing a getter function yourself does not do any harm as well.

Upvotes: 1

Related Questions