Pipes in Angular are a great way to transform and format data right from your templates. Out of the box you get pipes for dates, currency, percentage and character cases, but you can also easily define custom pipes of your own.This is where the concept of creating a Custom Pipe in Angular comes into play, allowing us to nicely extend our applications.
Angular provides some helpful filters known as Pipes which makes it very easy to format or transform the data value according to our needs.
Pipes are used with a Pipe (|) character, it takes an input and returns a desired formatted output. Simple Right?
Angular has a few built-in Pipes that ship with the framework’s CommonModule, allowing us to make use of them in any module we’re writing.
Here are a few usual suspects we could encounter with Angular’s built-in Pipes:
In simple terms, pipes allow us to write display value transformation right inside the template. Angular comes with stock or built in pipes to transform your values to what you want the user to see. For instance, the Date Pipe, allows you to change the date format and other aspects related to the date.
That’s the essence of a Pipe!
So, how do we use a Pipe? Let’s assume some simple component HTML with a binding of a Date stamp:
<div>
<!-- Renders: 21/10/2019 -->
<p></p>
</div>
This could render out as above with the formatted Date. So that’s a valid use case for Pipes! We don’t really want to fetch data and then loop through it all and convert each date from a Date object to a String, as we’d lose native Date object functionality and be duplicating values. It’s super convenient to use a Pipe and let it parse out for us!
Now you’re ready to start venturing into Custom Pipes! This will allow us to use a function to create our own input and output based on what you’re supplying. Let’s dive in!
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></p>
<p></p>
</div>
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></p>
So, we need to name the pipe “filesize”.
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 {}
NOTE : 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
.
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.
As we’re using our Pipe via interpolation, this is the magic on how we’re given arguments in a Pipe.
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 -->
We can now demonstrate how to add your own custom arguments to custom Pipes.
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 -->
And that’s all you need to supply an argument to your custom Pipe. Multiple arguments are simply separated by :
, for example:
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 #developer