Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Pablo Fredrikson
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Pablo Fredrikson ( @pablokbs )

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

By 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.

Want to use code from this post? Check out the license.

Reader Comments

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel