Angular filters are one of the toughest concepts to work with. They’re a little misunderstood and it actually hurt my brain whilst learning them. Filters are insanely great, they’re very powerful for transforming our data so easily into reusable and scalable little chunks.
I think it’s best to understand what we even want to learn. To do that, we need to understand what filters really are and how we use them. For me, there are four types of filters. Yes, four, but there of course can be other variants. Let’s rattle through them:
Table of contents
Filter 1: Static (single) use filter
Filter 1 just filters a single piece of Model data (not a loop or anything fancy) and spits it out into the View for us. This could be something like a date:
<p>{{ 1400956671914 | date: 'dd-MM-yyyy' }}</p>
When rendered, the DOM would look like this:
<p>24-05-2014</p>
So how do we create this, or something similar?
Let’s take my full name for example, if I wanted to quickly filter it and make it uppercase, how would we do it?
Angular has a .filter()
method for each Module, which means we can write our own custom filters. Let’s look at a stripped down filter:
app.filter('', function () {
return function () {
return;
};
});
As you can see, we can name our filter and we return
a function. What the heck do these do?
The returned function gets invoked each time Angular calls the filter, which means two-way binding for our filters. The user makes a change, the filter runs again and updates as necessary. The name of our filter is how we can reference it inside Angular bindings.
Let’s fill it in with some data:
app.filter('makeUppercase', function () {
return function (item) {
return item.toUpperCase();
};
});
So what do these mean? I’ll annotate:
// filter method, creating `makeUppercase` a globally
// available filter in our `app` module
app.filter('makeUppercase', function () {
// function that's invoked each time Angular runs $digest()
// pass in `item` which is the single Object we'll manipulate
return function (item) {
// return the current `item`, but call `toUpperCase()` on it
return item.toUpperCase();
};
});
As an example app:
var app = angular.module('app', []);
app.filter('makeUppercase', function () {
return function (item) {
return item.toUpperCase();
};
});
app.controller('PersonCtrl', function () {
this.username = 'Todd Motto';
});
Then we declare it in the HTML:
<div ng-app="app">
<div ng-controller="PersonCtrl as person">
<p>
{{ person.username | makeUppercase }}
</p>
</div>
</div>
And that’s it, jsFiddle link and output:
Free eBook
Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.
- Observables and Async Pipe
- Identity Checking and Performance
- Web Components <ng-template> syntax
- <ng-container> and Observable Composition
- Advanced Rendering Patterns
- Setters and Getters for Styles and Class Bindings
Filter 2: Filters for repeats
Filters are really handy for iterating over data and without much more work, we can do exactly that.
The syntax is quite similar when filtering a repeat, let’s take some example data:
app.controller('PersonCtrl', function () {
this.friends = [{
name: 'Andrew'
}, {
name: 'Will'
}, {
name: 'Mark'
}, {
name: 'Alice'
}, {
name: 'Todd'
}];
});
We can setup a normal ng-repeat
on it:
<ul>
<li ng-repeat="friend in person.friends">
{{ friend }}
</li>
</ul>
Add a filter called startsWithA
, where we only want to show names in the Array beginning with A
:
<ul>
<li ng-repeat="friend in person.friends | startsWithA">
{{ friend }}
</li>
</ul>
Let’s create a new filter:
app.filter('startsWithA', function () {
return function (items) {
var filtered = [];
for (var i = 0; i < items.length; i++) {
var item = items[i];
if (/a/i.test(item.name.substring(0, 1))) {
filtered.push(item);
}
}
return filtered;
};
});
There are two different things happening here! First, item
previously is now items
, which is our Array passed in from the ng-repeat
. The second thing is that we need to return a new Array. Annotated:
app.filter('startsWithA', function () {
// function to invoke by Angular each time
// Angular passes in the `items` which is our Array
return function (items) {
// Create a new Array
var filtered = [];
// loop through existing Array
for (var i = 0; i < items.length; i++) {
var item = items[i];
// check if the individual Array element begins with `a` or not
if (/a/i.test(item.name.substring(0, 1))) {
// push it into the Array if it does!
filtered.push(item);
}
}
// boom, return the Array after iteration's complete
return filtered;
};
});
ES5 version using Array.prototype.filter
for a super clean filter:
app.filter('startsWithA', function () {
return function (items) {
return items.filter(function (item) {
return /a/i.test(item.name.substring(0, 1));
});
};
});
And that’s it, jsFiddle link and output:
Filter 3: Filters for repeats with arguments
Pretty much the same as the above, but we can pass arguments into the functions from other Models. Let’s create an example that instead of “filtering by letter A”, we can let the user decide, so they can type their own example:
<input type="text" ng-model="letter">
<ul>
<li ng-repeat="friend in person.friends | startsWithLetter:letter">
{{ friend }}
</li>
</ul>
Here I’m passing the filter the letter
Model value from ng-model="letter"
. How does that wire up inside a custom filter?
app.filter('startsWithLetter', function () {
return function (items, letter) {
var filtered = [];
var letterMatch = new RegExp(letter, 'i');
for (var i = 0; i < items.length; i++) {
var item = items[i];
if (letterMatch.test(item.name.substring(0, 1))) {
filtered.push(item);
}
}
return filtered;
};
});
The most important thing to remember here is how we’re passing in arguments! Notice letter
now exists inside the return function (items, letter) {};
? This corresponds directly to the :letter
part. Which means we can pass in as many arguments as we need (for example):
<input type="text" ng-model="letter">
<ul>
<li ng-repeat="friend in person.friends | startsWithLetter:letter:number:somethingElse:anotherThing">
{{ friend }}
</li>
</ul>
We’d then get something like this:
app.filter('startsWithLetter', function () {
return function (items, letter, number, somethingElse, anotherThing) {
// do a crazy loop
};
});
And that’s it, jsFiddle link and output:
Filter 4: Controller/$scope filter
This one’s a bit of a cheat, and I would only use it if you really have to use it. We take advantage of the :arg
syntax and pass a $scope
function into Angular’s filter
Object!
The difference with these type of filters is that the functions declared are the ones passed into the filter function, so we’re technically writing a function that gets passed into our return function. We don’t get Array access, just the individual element. Important to remember.
Let’s create another function that filters by letter w
instead. First let’s define the function in the Controller:
app.controller('PersonCtrl', function () {
// here's our filter, just a simple function
this.startsWithW = function (item) {
// note, that inside a Controller, we don't return
// a function as this acts as the returned function!
return /w/i.test(item.name.substring(0, 1));
};
this.friends = [{
name: 'Andrew'
}, {
name: 'Will'
}, {
name: 'Mark'
}, {
name: 'Alice'
}, {
name: 'Todd'
}];
});
Then the repeat:
<div ng-controller="PersonCtrl as person">
<ul>
<li ng-repeat="friend in person.friends | filter:person.startsWithW">
{{ friend }}
</li>
</ul>
</div>
These functions are obviously scoped and not reusable elsewhere. If it makes sense then use this setup, else don’t…
And that’s it, jsFiddle link and output:
Happy filtering ;)