Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: David Bainbridge
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: David Bainbridge ( @redwhitepine )

Using Scope.$watchCollection() To Watch Functions In AngularJS

By Ben Nadel on

In the past, I've looked at using the Scope.$watch() method to watch a function, as opposed to a string, in AngularJS. Watching a function gives you fine-grained control over which value is being examined. I wanted to add a quick follow-up to that post to demonstrate that the same thing could be done with the Scope.$watchCollection() method. Instead of watching a string, Scope.$watchCollection() can be used to watch a function that returns an Array or an Object instance.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

Scope.$watchCollection() will perform a shallow watch on an array or an object. Most of the time, this is used to watch a string value that is taken out of a directive attribute. But, watching a function allows us to pull those values from anywhere, including, but not limited to, the scope. To see this in action, I've set up a demo in which we are watching a function that returns an array literal. The array contains only private variables, scoped to the controller. We're then going to change the embedded values over time to see when the watch-callback is invoked.

  • <!doctype html>
  • <html ng-app="Demo">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Using Scope.$watchCollection() To Watch Functions In AngularJS
  • </title>
  • </head>
  • <body ng-controller="AppController">
  •  
  • <h1>
  • Using Scope.$watchCollection() To Watch Functions In AngularJS
  • </h1>
  •  
  • <p>
  • <em>All the action is in the console.</em>
  • </p>
  •  
  • <!-- Load scripts. -->
  • <script type="text/javascript" src="../../vendor/angularjs/angular-1.4.3.min.js"></script>
  • <script type="text/javascript">
  •  
  • // Create an application module for our demo.
  • var app = angular.module( "Demo", [] );
  •  
  •  
  • // --------------------------------------------------------------------------- //
  • // --------------------------------------------------------------------------- //
  •  
  •  
  • // I control the root for the application.
  • angular.module( "Demo" ).controller(
  • "AppController",
  • function( $scope, $interval ) {
  •  
  • var a = 1;
  • var b = 1;
  • var friends = [
  • {
  • name: "Sarah"
  • },
  • {
  • name: "Tricia"
  • }
  • ];
  •  
  • // Rather than watching a String value, we're going to watch a Function.
  • // The $watchCollection() method is expecting to find either an Array
  • // (it will watch the indices) or an Object (it will watch the keys).
  • // However, since we are using function instead of a string, it doesn't
  • // have to watch values that are accessible off of the Scope. In this
  • // case, we're going to be watching private value (to prove a point).
  • $scope.$watchCollection(
  • function getValue() {
  •  
  • // Return an array composed of any values that we want. The
  • // contents of this array will be SHALLOWLY compared on each
  • // run of the digest.
  • return( [ a, b, friends[ 0 ] ] );
  •  
  • },
  • function collectionChanged( newValue, oldValue ) {
  •  
  • console.log(
  • "New value: %s , Was value: %s.",
  • JSON.stringify( newValue ),
  • JSON.stringify( oldValue )
  • );
  •  
  • }
  • );
  •  
  •  
  • // Increment the values in the collection over time.
  •  
  • $interval(
  • function incrementA() {
  •  
  • // NOTE: Even though this is triggering a digest, you will see
  • // that it doesn't actually trigger the watch since the value
  • // doesn't change.
  • a += 0;
  •  
  • },
  • 1000
  • );
  •  
  • $interval(
  • function incrementB() {
  •  
  • b += 1;
  •  
  • },
  • ( 7 * 1000 )
  • );
  •  
  • $interval(
  • function incrementFriends() {
  •  
  • // NOTE: We are changing the object REFERENCE in friends[ 0 ].
  • // This is to show that this works for object references as well
  • // as for simple value comparisons.
  • friends.unshift( friends.pop() );
  •  
  • },
  • ( 11 * 1000 )
  • );
  •  
  • }
  • );
  •  
  • </script>
  •  
  • </body>
  • </html>

As you can see, our $watchCollection() function is watching an array that contains two simple values and an object value. This mixed data collection is here to demonstrate that this will watch object reference changes. And, when we let this run for a while, we can see that our $interval() updates trigger the $watchCollection() callback:


 
 
 

 
 Watching an array literal with Scope.$watchCollection() in AngularJS. 
 
 
 

As you can see, the $watchCollection() callback was triggered whenever we changed either the simple values or the object reference. Notice, however, that the "a+=0" never triggered a callback as it never changed the value.

I have found the $watchCollection() method to be very helpful when I need to watch a collection of values that are passed into an isolate-scope directive. For example, if I need to watch 3 out of 5 attributes for changes, I just setup a function that returns an array that contains the 3 target values. AngularJS takes care of the rest.




Reader Comments

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.