Creating a custom filter (pipe) in Angular

This guide was written for Angular 2 version: 2.0.0-rc.5

Filters are a fantastic way of returning new collections of data, rather than mutating existing. Filters essentially are just functions, that accept a single value, or collection, and return a new value or collection based on that filter's responsibility. In this guide, we'll be covering how to create a custom pipe that accepts a single value and returns a new value, as well as passing arguments into filter functions.

Table of contents

Angular 1.x

In Angular 1.x, creating a filter is simply done by passing a pure function into the .filter() API. For this guide, we'll be using an "ordinal" filter, which takes a value such as this:

<!-- template code -->
<p>You came {{ '1' }}</p>
<p>You came {{ '2' }}</p>

Into this:

<!-- when compiled -->
<p>You came 1st</p>
<p>You came 2nd</p>

Therefore, our ordinal filter will return a new value with the correct suffix to the number value passed into the filter. Angular's .filter() API expects a function, that the first argument is the value the filter was bound to, and returns a new value, for example to demonstrate creating an uppercase filter:

const uppercase = () => {
  // filter function closure
  // `value` is passed to us
  return value => {
    // do something with the `value`
    var newValue = value.toUpperCase();
    // return a new value
    return newValue;
  };
};

angular
  .module('app')
  .filter('uppercase', uppercase);

We create the filter function and just pass it off to the .filter() API to get it registered.

Creating a custom filter

Let's get the ball rolling with our custom ordinal filter, I've already written the logic to implement it, and we don't need to focus on the internal details, just the Angular API. So, here's the function for our

const ordinal = () => {
  return value => {
    var suffix = '';
    var last = value % 10;
    var specialLast = value % 100;
    if (!value || value < 1) {
      return value;
    }
    if (last === 1 && specialLast !== 11) {
      suffix = 'st';
    } else if (last === 2 && specialLast !== 12) {
      suffix = 'nd';
    } else if (last === 3 && specialLast !== 13) {
      suffix = 'rd';
    } else {
      suffix = 'th';
    }
    return value + suffix;
  };
};

angular
  .module('app')
  .filter('ordinal', ordinal);

Using filters in templates

To use the above ordinal filter, all we need to do is use the pipe character inside our expression. For this, we'll create a simple component with an ng-repeat to iterate over an Array of numbers to print out 1st, 2nd, 3rd and so on.

const app = {
  template: `
    <div>
      <ul>
        <li ng-repeat="num in $ctrl.numbers">
          {{ num | ordinal }}
        </li>
      </ul>
    </div>
  `,
  controller() {
    this.numbers = [
      1,2,3,4,5,6,7,8,9,10,
      11,12,13,14,15,16,17,18,19,20
    ];
  }
};

angular
  .module('app')
  .component('app', app);

You can check out the full compiled out demo below, but next we'll dive into passing arguments into filters.

Passing arguments to filters

Passing arguments to filters is generally how we'll use them, we want to ensure filters are filtering based on something dynamic. With the .filter() API, we can specify further function arguments to be able to pass more information into filters:

const ordinal = () => {
  // passing another argument
  return (value, anotherValue) => {
    // do something with `value` and `anotherValue`
    // and return a new value
  };
};

angular
  .module('app')
  .filter('ordinal', ordinal);

The way we pass arguments into functions inside our templates is as follows:

const app = {
  template: `
    <div>
      <input ng-model="searchValue">
      <ul>
        <li ng-repeat="num in $ctrl.numbers">
          {{ num | ordinal:searchValue }}
        </li>
      </ul>
    </div>
  `,
  ...
};

In the above example, the ng-model value from the <input> is being directly captured and passed into the ordinal filter as a function, separating the arguments with a : colon. This searchValue will then directly map across to the function argument anotherValue in the previous code example.

Filtering in Controllers with $filter()

We also have the ability to filter inside the component's controller, using the $filter injectable, in this case we can filter the Array of numbers before binding to the view, which means we also remove the | ordinal pipe value from the template as well:

const app = {
  template: `
    <div>
      <ul>
        <li ng-repeat="num in $ctrl.numbers">
          {{ num }}
        </li>
      </ul>
    </div>
  `,
  controller($filter) {
    let numbers = [
      1,2,3,4,5,6,7,8,9,10,
      11,12,13,14,15,16,17,18,19,20
    ];
    // iterate the existing collection before binding
    // returns a new filtered collection
    this.numbers = numbers.map(number => $filter('ordinal')(number));
  }
};

This technique of filtering in a Controller is most favoured in Angular 1.x due to performance reasons, you can read why here.

Angular 2

For Angular 2, we'll be using the same ordinal pipe and demonstrating how to create it. The Angular 2 API isn't as straightforward as Angular 1.x (where we just returned a function that acts as a functional filter). With Angular 2, we need a class and sprinkle some decorators, so let's get started!

Creating a custom pipe

To get setup, we need to import Pipe and PipeTransform from the Angular 2 core:

import { Pipe, PipeTransform } from '@angular/core';

Next, we need to export and decorator our class with the right metadata and also use implements PipeTransform:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'ordinal'
})
export class OrdinalPipe implements PipeTransform {

}

The next step is implementing a method named transform, of which is required to create custom Angular 2 pipes. In our case, we are expecting a number being passed in and a string as the return value:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'ordinal'
})
export class OrdinalPipe implements PipeTransform {
  transform(value: number): string {
    let suffix = '';
    let last = value % 10;
    let specialLast = value % 100;
    if (!value || value < 1) {
      return value;
    }
    if (last === 1 && specialLast !== 11) {
      suffix = 'st';
    } else if (last === 2 && specialLast !== 12) {
      suffix = 'nd';
    } else if (last === 3 && specialLast !== 13) {
      suffix = 'rd';
    } else {
      suffix = 'th';
    }
    return value + suffix;
  }
}

And that's the Angular 2 equivalent of creating a filter, so let's go implement it inside our component.

Using pipes in templates

To use our pipe, we can create a component, add our OrdinalPipe import to the @NgModule inside the declarations Array, and we're good to go.

import {Component, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {OrdinalPipe} from './ordinal.pipe';

@Component({
  selector: 'my-app',
  template: `
    <div>
      <ul>
        <li *ngFor="let num of numbers">
          {{ num | ordinal }}
        </li>
      </ul>
    </div>
  `,
})
export class App {
  constructor() {
    this.numbers = [
      1,2,3,4,5,6,7,8,9,10,
      11,12,13,14,15,16,17,18,19,20
    ];
  }
}

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App, OrdinalPipe ],
  bootstrap: [ App ]
})
export class AppModule {}

And the live demo:

We'll save @NgModule and other fun stuff above for another guide. Onto the function arguments in custom pipes!

Passing arguments to pipes

Passing arguments is pretty much the same in Angular 2:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'ordinal'
})
export class OrdinalPipe implements PipeTransform {
  // passing another argument
  transform(value: number, anotherValue: string): string {
    // do something with `value` and `anotherValue`
    // and return a new value
  }
}

Filtering in Component classes with pipes

Just like with Angular 1.x when using $filter() inside the controller, we can do something similar with Angular 2 pipes. First, we need to tell the component that it has a provider:

...
import {OrdinalPipe} from './ordinal.pipe';
@Component({
  selector: 'my-app',
  template: `
    ...
  `,
  providers: [OrdinalPipe]
})
...

Then we can use dependency injection to inject the OrdinalPipe into the constructor, which makes it available privately as this.pipe, where we can call this.pipe.transform():

export class App {
  constructor(private pipe: OrdinalPipe) {
    let numbers = [
      1,2,3,4,5,6,7,8,9,10,
      11,12,13,14,15,16,17,18,19,20
    ];
    this.numbers = numbers.map(number => this.pipe.transform(number));
  }
}

Comments