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.
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.
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);
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 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.
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.
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!
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.
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 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
}
}
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));
}
}
Todd Motto (@toddmotto) is a Google Developer Expert from England, UK. He's taught millions of developers world-wide through his blogs, videos, conferences and workshops. He focuses on teaching Angular and JavaScript courses over on Ultimate Courses.