In my never ending quest to do things the "proper" angular way, I have been reading a lot about how to have controllers observe the changes in models held in angular services.
Some sites say using a $watch on a controller is categorically wrong:
DON'T use $watch in a controller. It's hard to test and completely unnecessary in almost every case. Use a method on the scope to update the value(s) the watch was changing instead.
Others seem fine with it as long as you clean up after yourself:
The $watch function itself returns a function which will unbind the $watch when called. So, when the $watch is no longer needed, we simply call the function returned by $watch.
There are SO questions and other reputable sites that seem to say right out that using a $watch in a controller is a great way to notice changes in an angular-service-maintained model.
The https://github.com/angular/angular.js/wiki/Best-Practices site, which I think we can give a bit more weight to, says outright that $scope.$watch should replace the need for events. However, for complex SPA's that are handling upwards of 100 models and REST endpoints, choosing to use $watch to avoid events with $broadcast/$emit
could end up with lots of watches. On the other hand, if we don't use $watch, for non-trivial apps we end up tons of event spaghetti.
Is this a lose/lose situation? Is it a false choice between events and watches? I know you can use the 2-way binding for many situations, but sometimes you just need a way to listen for changes.
EDIT
Ilan Frumer's comment made me rethink what I'm asking, so perhaps instead of just asking whether it is subjectively good/bad to use a $watch in a controller, let me put the questions this way:
Which implementation is likely to create a performance bottleneck first? Having controllers listen for events (which had to have been broadcast/emitted), or setting up $watch
-es in controllers. Remember, large-scale app.
Which implementation creates a maintenance headache first: $watch
-es or events? Arguably there is a coupling (tight/loose) either way... event watchers need to know what to listen for, and $watch
-es on external values (like MyDataService.getAccountNumber()
) both need to know about things happening outside their $scope.
** EDIT over a year later **
Angular has changed / improved a lot since I asked this question, but I still get +1's on it, so I thought I would mention that in looking at the angular team's code, I see a pattern when it comes to watchers in controllers (or directives where there is a scope that gets destroyed):
$scope.$on('$destroy', $scope.$watch('scopeVariable', functionIWantToCall));
What this does it take what the $watch function returns - a function that can be called to deregister the watcher - and give that to the event handler for when the controller is destroyed. This automatically cleans up the watcher.
Whether watches in controllers are code smell or not, if you use them, I believe the angular team's use of this pattern should serve as a strong recommendation for how to use them.
Thanks!
I use both, because honestly, I view them as different tools for different problems.
I'll give an example from an application that I built. I had a complex WebSocket Service that received dynamic data models from a web-socket server. The service itself doesn't care what the model looks like, but, of course, the controller sure does.
When the controller is initiated, it set up a $watch
on the service data object so that it knew when it's particular data object had arrived (like waiting for Service.data.foo
to exist. As soon as that model came into existence, it was able to bind to it and crate a two-way data-bind to it, the watch became obsolete, and it was destroyed.
On the other side, the service was responsible for broadcasting certain events as well, because sometimes the client would receive literal commands from the server. For instance, the Server might request that the client send some metadata that was stored in the '$rootScope' throughout the application. an .on()
was set up in the $rootScope
during module.run()
step to listen for those commands from the server, gather needed information from other services, and call the WebSocket service back to send the data as requested. Alternatively, if I had done this using a $watch()
, I would have needed to set up some sort of arbitrary variable for it to watch, like metadataRequests
which I would need to increment every time I receive a request. A broadcast
achieves the same thing without having to live in permanent memory, like our variable would.
Essentially, I use a $watch()
when there is a specific value that I want to see change (especially if I need to know the before-and-after values), and I use events if there are more high-level conditions that have been met that the controllers need to know about.
With regards to performance, I couldn't tell you which one is going to bottleneck first, but I feel like thinking of it this way will let you use the strengths of each feature where they are strongest. For instance, if you use $on()
instead of $watch()
to look for changes in data, you will not have access to the values before and after the change, which could limit what you are trying to do.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With