Filters in Angular massively contribute to slow performance, so let’s adopt a sensible way of doing things, which may take you an additional ten minute to code, but will dramatically enhance your application’s performance.
Let’s look at how removing filters in the DOM actually impacts our
Table of contents
What’s a DOM filter?
A DOM filter is where we use a
| pipe inside an expression to filter data:
This might output a String
'TODD' as we’ve used the
| uppercase filter. If you’re not well versed with filters, check out my article on custom Filters.
Why are DOM filters bad?
Take this example, it uses a DOM filter on an
ng-repeat, type some letters to see how it filters:
This looks great, and has no immediate performance concerns, no page lag or input delay. However let’s dig a little deeper.
Filter $digest evaluation
For us Angular developers, DOM filters are branded as the “awesomeness” that ships with the core, and by all means, this is tremendous, but there’s actually very little about the performance impacts of filters.
Are you ready to realise how your
$$watchers are being choked by using DOM filters? Okay here goes.
Let’s setup a basic test filter:
testFilter will be bound to an
ng-repeat, and each time the filter is called, it’ll increment its internal counter and log it out in the console for us.
We can add it to the
ng-repeat like so:
Let’s also add some data to a Controller and force a
~1000ms to see how quickly these filters start stacking up.
And the output (note the “Filter count: X” text in the code embed). Please note, the
<input ng-model=""> functionality no longer exists however has an
ng-model attribute bound, this is merely just to show filter evaluations inside the
Boom boom boom. Five filter calls every
$digest loop. This can’t be very efficient right? Especially with an
1000 items inside. Think about it.
What’s more, typing something inside the
<input ng-model=""> runs a
$digest and numbers soar into double and triple figures. By the time you’ve read down to this stage the figure is probably into the hundreds, if not thousands.
Why are our filters running when we’re not even filtering the
ng-repeat with the
The filters here are run every single
$filter in Controller
We almost certainly don’t want the desired behaviour above, which if you’ve got large collections inside an
ng-repeat and DOM filters, this is going to grind your application’s performance to a halt very quickly.
Let’s look at implementing a filter in the Controller using
And bind the
this.updateUsers method to the
<input ng-model=""> as an
Yes, we’re still forcing a
~1000ms in this example. Let’s observe the output and see when our
ng-repeat filter gets called…
Answer: no filters are run until you type, rather than every
$digest. We’re making serious progress. Our filter is only called when we activate it through the
this.updateUsers function, it’s not cycled through each
Okay that’s great, but how can I run my filter so it’ll work?
this.updateUsers method takes an argument
(username), we can pass this into Angular’s generic (and so wonderfully named)
And that’s all we need. Now we have a
$filter that is actually only called when we need to filter, rather than having the filter function run every single
$digest loop. This will significantly reduce the
$digest overhead when not filtering, and run filters when actually necessary.
Yes, chaining filters is great, but their order of declaration affects the output of the filtered collection, and two, you’re going to make the above
$digest filter issues even worse.
How can I chain filters inside a Controller?
Easy, it’s not essentially chaining, filters are just function calls. Filter your first collection, and pass the filtered collection off to another filter, then assign that finalised collection to the public property:
With this example, I can use
ng-init="user.updateUsers(username);" to instantly show you how this works:
The filter runs
$filter('filter')(users, username) followed by
$filter('orderBy')(filtered, 'name');. Which passes in
var filtered... as a variable. Essentially it’s doing this inside itself, passing a filter into a filter:
Filters in Controllers: do it.