Reader Stacks

What Is a Pipe in Angular, and How to Create a Custom One

A pipe is a pure, stateless transformation applied right in the template — the same job a component method could technically do, but a pipe is reusable across any template and reads more clearly at the call site.

A pipe transforms a value directly in the template's display syntax — Angular ships several built-in ones (date, currency, uppercase), and writing a custom one follows the same pattern for any transformation that isn't already covered.

Using a built-in pipe

{{ order.createdAt | date:'medium' }}
{{ product.price | currency:'USD' }}
{{ user.name | uppercase }}

Generating a custom pipe with the CLI

ng generate pipe truncate

A basic custom pipe

@Pipe({
    name: 'truncate'
})
export class TruncatePipe implements PipeTransform {
    transform(value: string, limit: number = 50): string {
        if (value.length <= limit) return value;
        return value.slice(0, limit) + '...';
    }
}
{{ post.excerpt | truncate:100 }}

The transform() method's first parameter is always the value being piped (whatever appears to the left of the |), and any arguments after a colon in the template (:100 here) map to its subsequent parameters — the same pattern every built-in pipe follows.

A pipe with multiple parameters

@Pipe({
    name: 'truncate'
})
export class TruncatePipe implements PipeTransform {
    transform(value: string, limit: number = 50, suffix: string = '...'): string {
        if (value.length <= limit) return value;
        return value.slice(0, limit) + suffix;
    }
}
{{ post.excerpt | truncate:100:' [more]' }}

Chaining multiple pipes together

{{ post.title | truncate:30 | uppercase }}

Pipes chain left to right — each one receives the output of the previous one as its input, so the order they're listed in genuinely matters for the final result.

Why pipes must be pure by default

@Pipe({
    name: 'truncate',
    pure: true // this is the default
})

A pure pipe's transform() re-runs only when its actual input value or arguments change (by reference, for objects and arrays) — this is a deliberate performance optimization, since Angular would otherwise need to re-run every pipe on every single change detection cycle regardless of whether its inputs actually changed.

Creating an impure pipe (used sparingly)

@Pipe({
    name: 'filterActive',
    pure: false
})
export class FilterActivePipe implements PipeTransform {
    transform(items: Item[]): Item[] {
        return items.filter(item => item.isActive);
    }
}

Marking a pipe impure makes it re-run on every change detection cycle, even if its input array reference hasn't changed — necessary if the pipe needs to react to mutations inside an array rather than reassignments of the array itself, but genuinely costly for performance if overused, since it defeats the whole point of the pure-by-default optimization.

Why a pipe, rather than just a component method

A component method bound in the template ({{ truncate(post.excerpt) }}) would actually re-run on every single change detection cycle regardless of purity — a pure pipe's built-in memoization is specifically what a plain method call in a template binding doesn't get for free, which is the real practical reason to reach for a pipe over a method for this kind of transformation.