Step by Step Custom Pipes in Angular

Angular Tagged in Angular Jan 13, 2018 4 mins read by Todd Motto

Angular has many Pipes built-in, but they only take us so far. Ideally we’d like to extend our applications by creating custom Pipes.

Custom Pipes (previously Filters in AngularJS) allow us to essentially create a pure function, which accepts an input and returns a different output via some form of transformation.

That’s the essence of a Pipe. And we see this already in Angular with things like the Date Pipe and friends.

There are various ways we can create Pipes, so let’s explore them a little further.

A Custom Pipe in Angular

The most basic pipe transforms a single value, into a new value. This value can be anything you like, a string, array, object, etc.

For the demonstration of this, we’ll be converting numeric filesizes into more human readable formats, such as “2.5MB” instead of something like “2120109”. But first, let’s start with the basics - how we’ll use the Pipe.

Pipe usage

Let’s assume an image was just uploaded via a drag and drop zone - and we’re getting some of the information from it. A simplified file object we’ll work with:

export class FileComponent {
  file = { name: 'logo.svg', size: 2120109, type: 'image/svg' };
}

Properties name and type aren’t what we’re really interested in to learn about Pipes - however size is the one we’d like. Let’s put a quick example together for how we’ll define the usage of our pipe (which will convert numbers into filesizes):

<div>
  <p>{{ file.name }}</p>
  <p>{{ file.size | filesize }}</p>
</div>

Pipe class and decorator

To create a Pipe definition, we need to first create a class (which would live in its own file). We’ll call this our FileSizePipe, as we are essentially transforming a numeric value into a string value that’s more human readable:

export class FileSizePipe {}

Now we’ve got this setup, we need to name our Pipe. In the above HTML, we did this:

<p>{{ file.size | filesize }}</p>

So, we need to name the pipe “filesize”. This is done via another TypeScript decorator, the @Pipe:

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

@Pipe({ name: 'filesize' })
export class FileSizePipe {}

All we need to do is supply a name property that corresponds to our template code name as well (as you’d imagine).

Don’t forget to register the Pipe in your @NgModule as well, under declarations:

// ...
import { FileSizePipe } from './filesize.pipe';

@NgModule({
  declarations: [
    //...
    FileSizePipe,
  ],
})
export class AppModule {}

Pipes tend to act as more “utility” classes, so it’s likely you’ll want to register a Pipe inside a shared module. If you want to use your custom Pipe elsewhere, simply use exports: [YourPipe] on the @NgModule.

Pipe and PipeTransform

Once we’ve got our class setup, registered, and the @Pipe decorator added - the next step is implementing the PipeTransform interface:

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

@Pipe({ name: 'filesize' })
export class FileSizePipe implements PipeTransform {
  transform() {}
}

This creates a required contract that our FileSizePipe must adhere to the following structure:

export interface PipeTransform {
  transform(value: any, ...args: any[]): any;
}

Which is why we added the transform() {} method to our class above.

Pipe Transform Value

As we’re using our Pipe via interpolation, this is the magic on how we’re given arguments in a Pipe.

{{ file.size | filesize }}

The file.size variable is passed straight through to our transform method, as the first argument.

We can call this our size and type it appropriately:

//...
export class FileSizePipe implements PipeTransform {
  transform(size: number) {}
}

From here, we can implement the logic to convert the numeric value into a more readable format of megabytes.

//...
export class FileSizePipe implements PipeTransform {
  transform(size: number): string {
    return (size / (1024 * 1024)).toFixed(2) + 'MB';
  }
}

We’re returning a type string as we’re appending 'MB' on the end. This will then give us:

<!-- 2.02MB -->
{{ file.size | filesize }}

We can now demonstrate how to add your own custom arguments to custom Pipes.

Pipes with Arguments

So let’s assume that, for our use case, we want to allow us to specify the extension slightly differently than advertised.

Before we hit up the template, let’s just add the capability for an extension:

//...
export class FileSizePipe implements PipeTransform {
  transform(size: number, extension: string = 'MB'): string {
    return (size / (1024 * 1024)).toFixed(2) + extension;
  }
}

I’ve used a default parameter value instead of appending the 'MB' to the end of the string. This allows us to use the default 'MB', or override it when we use it. Which takes us to completing our next objective of passing an argument into our Pipe:

<!-- 2.02megabyte -->
{{ file.size | filesize:'megabyte' }}

And that’s all you need to supply an argument to your custom Pipe. Multiple arguments are simply separated by :, for example:

{{ value | pipe:arg1 }}
{{ value | pipe:arg1:arg2 }}
{{ value | pipe:arg1:arg3 }}

Don’t forget you can chain these pipes alongside others, like you would with dates and so forth.

Here’s the final assembled code:

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

@Pipe({ name: 'filesize' })
export class FileSizePipe implements PipeTransform {
  transform(size: number, extension: string = 'MB') {
    return (size / (1024 * 1024)).toFixed(2) + extension;
  }
}

Want a challenge? Extend this custom Pipe that allows you to represent the Pipe in Gigabyte, Megabyte, and any other formats you might find useful. It’s always a good exercise to learn from a starting point!

Angular Online Courses Angular shields
Ultimate Angular

Everything you need to master Angular

Online courses that give you the knowledge to master Angular and build real world applications. Join 45,000 others today.

Start now Navigation arrow