4

I need to know why is the $watch function used if the AngularJS automatically creates a watch for every model (ng-model) that is created.

(When I say model, I refer to a value in a controller declared like `$scope.value = "somethign" edit-> that is attached to the view by {{}} or ng-model)

My guess is:

1) It is useful to create complex actions when a change is detected on the model. Not just changing the value in the view.

2) It is used to add watchers to values that are NOT binded with the view as models. (This can work out, but is it a good practice?)

That is just my guess, I'd like to know if I'm right.

Also, if there is any advanced way to use the $watch, do tell please.

Thanks.

AFP_555
  • 2,392
  • 4
  • 25
  • 45

2 Answers2

4

As you said, Angular creates a $watch for every binding on screen. That includes both ng-model and {{ ... }} (and ng-repeat and others directives).

When do you want to create your own $watch functions?

Your first guess is a good one. If you need to run some code when a model change, you need to put a watch on it.

Imagine you have a directive that shows some charts and that directives receives via attributes some data. The idea is to do some calculation when new data arrives. How do you do that? You need to watch that data. When the data changes, the $watch fires and you do the calculation, then the DOM is updated.

See an example here

Your second point could be useful too. A $watch can return any kind of code like..

$scope.$watch(function() { return service.foo; }, function() { ... });

so if foo on the service changes, the $watch will trigger. Can be handy on certain use cases.

Jesus Rodriguez
  • 11,918
  • 9
  • 64
  • 88
  • I have another question Jesus. What happens when a watcher is added automatically to the digest as discussed, but then the user adds another watcher to that same variable. Isn't this harmful for the digest loop performance? – AFP_555 Jul 02 '14 at 23:20
  • Any $watch adds a performance hit to a page. 100 watch won't hurt, maybe 1000 won't hurt either, you just need to make your watch fast, AKA not putting heavy task on them. You can add as many watch as you want onto a variable, all of them will run. In your case, you can have a ng-model's $watch but also another watch on it to trigger something when that model changes. – Jesus Rodriguez Jul 03 '14 at 00:00
1

AngularJS does not automatically create watch functions, unless you (implicitly) tell it to do so. These implicit methods are by adding it to your template in expression language or by using it in a directive.

Moreover, the $scope.$watch function is very useful to run a piece of code when a value changes. I don't see why you should add a watcher to a value that s not binded to a view. This typically would change with a function or when it's triggered by another value that is being watched. This would be a good case for using $scope.$watch.

Read more about the digest cycle of AngularJS and the $watch and $apply functions in this SO-question: How do I use $scope.$watch and $scope.$apply in AngularJS?

Community
  • 1
  • 1
jvdp
  • 149
  • 8
  • I'm sorry, when I said that a watch is automatically added to the digest loop, I didn't mean it based on the definition of model that I gave, I meant it to ng-models. "value" was added to the diggest loop, wasn't it? – AFP_555 Jul 02 '14 at 10:45
  • Well, it does not have to watch the input field up until you do something with it. So that is when you use the model in another function, or an action that is linked to the field is fired. Check the following plunkr adopted from the Angular docs: http://plnkr.co/edit/JKj1MoXKMdumesiT2DhP?p=preview . If you would not have an event that watches the changes of the ng-models, the value of the concatenation of them would not change. – jvdp Jul 02 '14 at 11:17
  • I don't fully agree with the answer of Jesus, it does not implicitly watch ng-model, as you can find out in the plunkr. Besides that, nice and more complete answer than mine. – jvdp Jul 02 '14 at 11:23
  • Jesus is right, the mistake was mine when I said "..view by {{}} OR ng-model". It is not OR, it is AND. In his comment Jesus says "BOTH", and in the plunker there is no ng-model="greeting", that is why it is not being added to the digest loop automatically. Maybe if he removed the dot (.) between the "...screen" and "That", it would make better sense. – AFP_555 Jul 02 '14 at 12:43
  • No, $scope.greeting is added as expression language: {{greeting}}, thus it is added to the digest loop. Ng-model is not implicitly added to the digest loop. Try the following: create two input fields with ng-models, ng-model="a" and ng-model="b". Then add {{c}} to your template. In the js file you add the line $scope.c = $scope.a + $scope.b. If there would be a watch on a and b, c should change automatically as soon as you change a or b, but it doesn't. Hence, there is no implicit watch on ng-model. Check the adapted plnkr: http://plnkr.co/edit/JKj1MoXKMdumesiT2DhP?p=preview – jvdp Jul 02 '14 at 12:55
  • No no no, `ng-model` also creates a watch. All directive that does databinding creates a watch. ng-model is one of them. It is created to watch changes on the $scope to update the view. Check this https://github.com/angular/angular.js/blob/d3c191ea637cc3ad56cd393ba1897f9d260709e9/src/ng/directive/input.js#L1896 – Jesus Rodriguez Jul 02 '14 at 21:36
  • 1
    So about the `a`, `b`, `c` example. You can't do that. `a` and `b` gets updated when the ng-model changes, that is for sure. The problem is: `c` is using a reference to `a` + reference to `b` which returns some values. When you change any ng-model, the reference to `a` or `b` changes and `c` is still pointing to the old reference and because of that, it doesn't see any changes. Of course, the changes are changing those references and `c` can't keep up. You can use a "function" that will use the new reference every time: http://plnkr.co/edit/BGuMZ2tOj171N4TjjpLK?p=preview or... – Jesus Rodriguez Jul 02 '14 at 21:47
  • You can see here how with every stroke, C shows the values: http://plnkr.co/edit/JVO7nBdf2COaxsMHQa4R?p=preview. Not much more to say on comments, join #angularjs at freenode (IRC) and ping me (foxandxss) if you have more questions. – Jesus Rodriguez Jul 02 '14 at 21:48
  • I agree with Jesus. The reason why C is not being refreshed is this: ---When the digest loop finds a change in one of the watchers, it will refresh the variable or take some other action, then it runs again to check that that change didn't affect another watch-- In this case A and B were in the watch list and were refreshed and modified C, but C is not refreshing because it was not added to the digest loop just by displaying it as {{}} in the DOM. It needs the ng-model to get the watch added to the digest loop. – AFP_555 Jul 02 '14 at 23:11
  • That is not correct @user2545722. All three variables have a watch `c` one won't trigger again because it is being assigned to a primitive and that primitive is getting replaced every time you write on the inputs so the `c` watch is watching an old primitive that doesn't exist anymore. Read this: https://github.com/angular/angular.js/wiki/Understanding-Scopes – Jesus Rodriguez Jul 03 '14 at 00:04