Reader Stacks

Angular Pipes: Built-in and Custom

A pipe transforms a value for display, right in the template, without touching the underlying data — the built-in set covers formatting basics, and a custom pipe extends the same pattern to anything else.

A pipe takes a value and transforms it for display purposes, applied directly in the template with the | syntax — the underlying data in the component stays completely unchanged; only what's rendered on screen is affected.

1. Common built-in pipes

<p>{{ price | currency }}</p>              <!-- $1,234.56 -->
<p>{{ publishedAt | date: 'mediumDate' }}</p> <!-- Mar 9, 2024 -->
<p>{{ title | uppercase }}</p>
<p>{{ description | slice: 0:100 }}...</p>   <!-- truncate to the first 100 characters -->
<p>{{ user | json }}</p>                     <!-- pretty-printed JSON, mainly useful for debugging -->

2. Passing parameters to a pipe

{{ price | currency: 'EUR' }}
{{ price | currency: 'EUR': 'symbol': '1.2-2' }}

Parameters after the first colon customize the pipe's behavior — for currency, that includes the currency code, whether to show a symbol or code, and the minimum/maximum decimal digits, all without needing to hand-format the number in the component class.

3. Chaining multiple pipes together

{{ title | lowercase | titlecase }}

Pipes apply left to right — each one transforms the output of whatever came before it, so several small, focused pipes can compose into a more complex transformation without needing one pipe that tries to do everything.

4. Writing a custom pipe

ng generate pipe truncate
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'truncate',
})
export class TruncatePipe implements PipeTransform {
  transform(value: string, maxLength: number = 100): string {
    if (!value || value.length <= maxLength) {
      return value;
    }
    return value.slice(0, maxLength).trim() + '…';
  }
}
<p>{{ post.excerpt | truncate: 150 }}</p>

A custom pipe is a class implementing PipeTransform, with its actual logic in the required transform() method — the @Pipe decorator's name is what the template uses after the | symbol, and any additional method parameters beyond the piped value itself become the pipe's own configurable arguments in the template.

5. Pure vs. impure pipes — a real performance consideration

@Pipe({
  name: 'filterActive',
  pure: false, // opts into re-running on every change detection cycle
})
export class FilterActivePipe implements PipeTransform {
  transform(items: Item[]): Item[] {
    return items.filter((item) => item.isActive);
  }
}

By default, a pipe is "pure" — Angular only re-runs it when its actual input reference changes, not on every change detection cycle. A pipe filtering or transforming an array (like the example above) needs to be pure to work correctly if the array's contents mutate in place without creating a new array reference — but marking a pipe impure (as shown) makes it re-run considerably more often, which is a real performance cost worth being deliberate about rather than reaching for by default whenever something "doesn't seem to update."

6. When a pipe is the wrong tool

A pipe is meant for lightweight, display-only formatting — for anything involving an actual API call, complex business logic, or a transformation expensive enough to matter performance-wise, that logic belongs in the component class (or a service) instead, computed once and stored as a property, rather than re-run repeatedly through a pipe in the template on every change detection pass.

Topics: Developer Productivity