Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at TechCrunch Disrupt (New York, NY) with: Aaron Foss

How Often Do Filters Execute In AngularJS

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

Tweet This Great article by @BenNadel - How Often Do Filters Execute In AngularJS Thanks my man — you rock the party that rocks the body!



Reader 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 :)

Reply to this Comment

@soncu, I've been having mixed feelings about it myself. Can you explain what don't you like about it?

Reply to this Comment

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?

Reply to this Comment

@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

Reply to this Comment

@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.)

Reply to this Comment

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

Reply to this Comment

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

Reply to this Comment

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.

Reply to this Comment

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.