Skip to main content
Ben Nadel at BFusion / BFLEX 2009 (Bloomington, Indiana) with: Simon Free and Dee Sadler
Ben Nadel at BFusion / BFLEX 2009 (Bloomington, Indiana) with: Simon Free ( @simonfree ) Dee Sadler ( @DeeSadler )

How Often Do Filters Execute In AngularJS

By on

The other day, Lukas Ruebbelke wrote an excellent blog post on AngularJS filters, including some very clever ways in which to use them to output nested lists. Admittedly, I haven't looking into filters very much. When I first saw them, I found the syntax confusing; and so, I never really pursued them. But, Lukas' post was quite compelling, so I figured filters were worth looking into. And, the first thing that I wanted to see was, how often do filters actually execute in AngularJS?

The short answer: a lot.

From what I can tell, a filter is executed once during every $digest. This makes sense, when you think about how AngularJS manages its data bindings. Since AngularJS uses dirty-checking, it has to recheck all of its internal $watch() statements after "you" (as the programmer / user) do anything. After all, every interaction taken by the user may cause a change in the view-model which may, in turn, change the collection that is being filtered. As such, the filter has to be re-applied at the end of every $digest.

To see this in action, I have put together a quick demo that applies a "random" filter to a collection of friends. This random filter will remove items from the collection based on the current time. In addition to the filter, I have also created two means of triggering a $digest - a click and a mousemove:

<!doctype html>
<html ng-app="Demo" ng-controller="AppController">
<head>
	<meta charset="utf-8" />

	<title>
		How Often Do Filters Execute In AngularJS
	</title>

	<style type="text/css">

		p.hotspot {
			border: 1px dotted #FF9900 ;
			height: 50px ;
			line-height: 50px ;
			text-indent: 10px ;
		}

	</style>
</head>
<body>

	<h1>
		How Often Do Filters Execute In AngularJS
	</h1>


	<!-- BEGIN: AngularJS Digest Triggers. -->
	<p>
		<a ng-click="triggerDigest()">Trigger Digest</a>
	</p>

	<p ng-mousemove="triggerDigest()" class="hotspot">
		Don't move your mouse here!
	</p>
	<!-- END: AngularJS Digest Triggers. -->


	<!-- BEGIN: List Of Friends. -->
	<ul>

		<li ng-repeat="friend in friends | filter: random">

			{{ friend.name }}

		</li>

	</ul>
	<!-- END: List Of Friends. -->


	<!-- Load jQuery and AngularJS from the CDN. -->
	<script
		type="text/javascript"
		src="//code.jquery.com/jquery-2.0.0.min.js">
	</script>
	<script
		type="text/javascript"
		src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular.min.js">
	</script>
	<script type="text/javascript">


		// Create an application module for our demo.
		var app = angular.module( "Demo", [] );


		// -------------------------------------------------- //
		// -------------------------------------------------- //


		// I control the root of the application.
		app.controller(
			"AppController",
			function( $scope ) {

				// Set up the default collection of friends.
				$scope.friends = [
					{
						id: 1,
						name: "Sarah"
					},
					{
						id: 2,
						name: "Joanna"
					},
					{
						id: 3,
						name: "Heather"
					},
					{
						id: 4,
						name: "Kim"
					}
				];


				// ---
				// PUBLIC METHODS.
				// ---


				// I provide the filter function being used to output
				// the list of friends.
				$scope.random = function( item ) {

					console.log( "Filtering on", item.name );

					var now = new Date();

					// If this returns true, the item is kept in the
					// list; otherwise, it is filtered out.
					return( now.getTime() % item.id );

				};


				// I am simply here to trigger a $digest. After the
				// click-handler executes, AngularJS will perform a
				// dirty check on all bindings to see if the click
				// handlers percipitated a change in the view-model.
				$scope.triggerDigest = function() {

					console.log( "# Thou hast summoned the $digest #" );

				};

			}
		);


	</script>

</body>
</html>

Notice that the triggerDigest() method doesn't actually do anything. But, AngularJS doesn't "know" this. For all AngularJS knows, the triggerDigest() method may have added or removed items from the Friends collection. As such, it must perform a dirty check on the collection, re-applying the filter.

When we run the above code, and I click on the "Trigger Digest" link a few times, I get the following console output:

# Thou hast summoned the $digest #
Filtering on Sarah
Filtering on Joanna
Filtering on Heather
Filtering on Kim
Filtering on Sarah
Filtering on Joanna
Filtering on Heather
Filtering on Kim
# Thou hast summoned the $digest #
Filtering on Sarah
Filtering on Joanna
Filtering on Heather
Filtering on Kim
# Thou hast summoned the $digest #
Filtering on Sarah
Filtering on Joanna
Filtering on Heather
Filtering on Kim
# Thou hast summoned the $digest #
Filtering on Sarah
Filtering on Joanna
Filtering on Heather
Filtering on Kim
Filtering on Sarah
Filtering on Joanna
Filtering on Heather
Filtering on Kim
Filtering on Sarah
Filtering on Joanna
Filtering on Heather
Filtering on Kim
Filtering on Sarah
Filtering on Joanna
Filtering on Heather
Filtering on Kim

As you can see, every time I clicked the "Trigger Digest" link, the filter was re-applied. And, in fact, it was often applied more than once during a full $digest lifecycle.

So what does this all mean? Well, it depends on the context. If you're using a small collection and your filtering is simple, then this probably doesn't mean anything at all. But, if your collection is large and your filtering logic is complex, then it's possible that filtering may cause a performance problem. In those cases (which would need to be taken on a case-by-case basis), you may need to manually filter your collections (with a $watch() statement) so that you have more control over when your filter actually gets applied.

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

Reader Comments

15,674 Comments

@Soncu,

AngularJS definitely has a learning curve! I will give you that. But, once you start to get comfortable with it, it's quite awesome! But, it takes you a while to get that feel :)

18 Comments

Since you've posted quite a bit about AngularJS, what are your feelings on using it in conjunction with a server-side framework like FW/1? Are there benefits to be gained from having a framework on each side of the pipe or would there be too much redundancy and/or conflict in the systems?

15,674 Comments

@Justin,

At InVisionApp, we actually use FW/1 on the server-side and AngularJS on the client-side. I have found no issues with the two frameworks working in conjunction. Since our platform is basically a Single Page Application (SPA), AngularJS talks to the FW/1 stuff via AJAX calls that return JSON values. So, it's more like passing data over the wall - no conflicts.

If there are any problems, it's just trying to wrap your head around HOW to build a single-page app.... which I am slowly getting better at every day :D

18 Comments

@Ben,

So effectively, FW/1 becomes an API for a data repo while AngularJS functions as the site proper.

If I'm understanding it correctly, then why use FW/1 at all? Wouldn't a large collection of remotely accessible CFCs accomplish the same task? (FWIW, I can think of some benefits - but I'd like to hear your take on it.)

15,674 Comments

@Justin,

Yeah, definitely - you could definitely do the same with remove CFCs (or just plain-old CFM files). In fact, this is the first project that I've ever used FW/1 and I've certainly worked on API-oriented projects in the past.

Traditionally, I've built server-side routing with simple CFSwitch / CFInclude tags (nice and simple). FW/1 is doing more or less the same stuff, but it is smarter about the caching and allows for some "common" / "shared" controllers, which is nice.

15,674 Comments

@L,

I cannot say from a technical standpoint if that is true or not; I will take your word for it. But, the matter is slightly more complicated than that. AngularJS, from my understanding, will continue to execute a $digest if any of the values within a digest change. This way, it can see if the changes affect expressions in the next digest (I think). This is why you will occasionally get the "10 digest limit", because data keeps changing and AngularJS has to continually fire-off subsequent digests.

So that said, even if a filter fires twice per digest, you must also take into account how often digests fire. In my demo, they fire a LOT because of the mouse-move event handler. So, you just have to look at things holistically.

1 Comments

Great post. I also encountered the issue when I built a page with dozen pair of lists that use a simple filter to separate items with positive and negative values to their own lists. Now any change to scope will fire two dozen filter actions even if nothing has changed.

I think Angular filters should have additional feature that they'd only fire when the underlying expression changes.

2 Comments

Seems like AngularJS 1.3 optimized the way filters are re-evaluated. It has the notion of stateless filters (the default) and stateful filters (not recommended). Stateless filters are only evaluated when the input changes. I've tested the difference between 1.2.19 and 1.3.2 (unstable) and it's working. 1.2.19 behaves like the way you described it, 1.3.2 doesn't re-evaluate stable input.

Have a look at this Plunker: http://plnkr.co/edit/hlf4bwkyMSXl5prpksKL

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