Tuesday, February 23, 2016

Mastering $watch in AngularJS

AngularJS offers many different options to use the publish-subscribe pattern through three different "watch" methods. Each of them takes optional parameters that can modify its behavior.

The official documentation on $watch is anything but thorough: a problem that has afflicted AngularJS v1 as whole, after all.
And even online resources explaining how to proceed are, at best, scattered.

So, in the end, it becomes hard for developers choosing the right method for a given situation. And that's especially true for AngularJS beginners! Results can be surprising or unpredictable, and this inevitably leads to bugs.

In this article I will assume some familiarity with AngularJS concepts. If you feel you need a refresher, you might want to read up on $scope, binding and $apply and $digest.

Check Your Understanding

For example, what's the best way to watch the first element of an array?

Suppose we have an array declared on our scope, $scope.letters = ['A','B','C'];

  • Will $scope.$watch('letters', function () {...}); fire its callback when we add an element to the array?
  • Will it when we change its first element?
  • What about $scope.$watch('letters[0]', function () {...});? Will it work the same, or better?
  • Above, arrays elements are primitive values: what if we replace the first element with the same value?
  • Now suppose the array holds objects instead: what happens?
  • What's the difference between $watch, $watchCollection, and $watchGroup?

If you feel confused by all these questions, please keep reading. My aim is to make this as clear as possible through several examples, guiding you along the way.

$scope.$watch

Let's start with $scope.$watch. This is the workhorse of all the watch functionality: every other method we will see is just a convenient shortcut for $watch.

$watch It

Now, what's great about Angular is that you can use the same mechanism explicitly to perform complex actions in your controllers triggered by data changes. For instance, you could set a watcher on some data that can change in response to:

  1. Timeouts
  2. UI
  3. Complex asynchronous computations performed by web workers
  4. Ajax calls

You can just set up a single listener to handle any data change, no matter what caused it.

To do so, however, you need to call $scope.$watch yourself.

Hands On

Let's take a look at the code for $rootscope.watch().

This is its signature: function(watchExp, listener, objectEquality, prettyPrintExpression).

In details, its four parameters:

  1. watchExp The expression being watched. It can be a function or a string, it is evaluated at every digest cycle.

    One key aspect to note here, is that if the expression is evaluated as a function, then that function needs to be idempotent. In other words, for the same set of inputs it should always return the same output. If this is not the case, Angular will assume that the data being watched has changed. In turn, this means that it will keep detecting a difference and call the listener at every iteration of the digest cycle.

  2. listener A callback, fired when the watch is first set, and then each time that during the digest cycle that a change for watchExp's value is detected. The initial call on setup is meant to store an initial value for the expression.

  3. objectEquality If, and only if, this is true the watcher will perform a deep comparison. Otherwise it performs a shallow comparison, i.e. only the references will be compared.

    Let's take an array as an example: $scope.fruit = ["banana", "apple"];.

    objectEquality == false means that only a reassignment to the fruit field will yield a call to the listener.

    We also need to check "how deep" is a deep comparison: we'll look at that later.

  4. prettyPrintExpression
    If passed, it overrides the watch expression. This parameter is NOT meant to be used in normal calls to $watch(); it is used internally by expression parser.

    Be careful: as you can see for yourself, it's very easy to run into unexpected results when passing a 4th parameter by mistake.

Continue reading %Mastering $watch in AngularJS%


by Marcello La Rocca via SitePoint

No comments:

Post a Comment