I'm Todd, I teach the world Angular through @UltimateAngular. Conference speaker and Developer Expert at Google.

Everything about custom filters in AngularJS

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.

For live AngularJS help on AirPair

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:

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:

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 ;)

Todd Motto

I'm Todd, I teach the world Angular through @UltimateAngular. Conference speaker and Developer Expert at Google.