Skip to content
Angular ng pipes 4 min read

Creating Custom Pipes

Angular’s built-in pipes cover formatting essentials like dates, currency, and JSON, but real applications inevitably need domain-specific transformations: truncating text, formatting phone numbers, filtering arrays, or rendering business-specific units. Custom pipes let you encapsulate that logic in a small, declarative, reusable class that plugs straight into your templates. A custom pipe is just a class decorated with @Pipe that implements the PipeTransform interface, and in modern Angular it is standalone by default.

Anatomy of a custom pipe

Every custom pipe needs two things: the @Pipe decorator that registers a template name, and a transform method that receives the input value plus any arguments and returns the transformed result. The PipeTransform interface enforces the correct method signature so TypeScript catches mistakes early.

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

@Pipe({
  name: 'truncate',
})
export class TruncatePipe implements PipeTransform {
  transform(value: string, limit = 25, trail = '…'): string {
    if (!value) return '';
    return value.length > limit ? value.substring(0, limit) + trail : value;
  }
}

The name property is what you reference in templates with the | operator. The first parameter of transform is always the piped value; every parameter after that maps to a colon-separated argument in the template.

Pipes are standalone by default in Angular 17+. There is no standalone: true flag to set and no NgModule declaration — you simply import the pipe class wherever you use it.

Registering and using the pipe

Because the pipe is standalone, you register it by adding the class to a component’s imports array. Then it is available in that component’s template like any built-in pipe.

import { Component } from '@angular/core';
import { TruncatePipe } from './truncate.pipe';

@Component({
  selector: 'app-article-card',
  imports: [TruncatePipe],
  template: `
    <h3>{{ title }}</h3>
    <p>{{ body | truncate }}</p>
    <p>{{ body | truncate: 10 }}</p>
    <p>{{ body | truncate: 10 : ' [more]' }}</p>
  `,
})
export class ArticleCardComponent {
  title = 'Working with Pipes';
  body = 'Angular pipes transform data directly in the template.';
}

Output:

Working with Pipes
Angular pipes transform da…
Angular pi…
Angular pi [more]

Notice how arguments are passed positionally, separated by colons: truncate: 10 : ' [more]'. The first colon maps to limit, the second to trail. Omitted arguments fall back to the defaults declared in transform.

Passing dynamic arguments

Pipe arguments are not limited to literals — you can bind any component property or expression, and the pipe re-runs whenever the bound input or argument changes (for pure pipes, which is the default).

@Component({
  selector: 'app-summary',
  imports: [TruncatePipe],
  template: `
    <input type="range" min="5" max="50" [(ngModel)]="maxLength" />
    <p>{{ description | truncate: maxLength }}</p>
  `,
})
export class SummaryComponent {
  description = 'Custom pipes keep transformation logic out of components.';
  maxLength = 20;
}

As the user drags the slider, maxLength updates and the pipe re-evaluates automatically, producing a live-truncated string with no manual subscription or change handler.

Generating a pipe with the CLI

Rather than hand-writing boilerplate, scaffold a pipe with the Angular CLI. It creates the class, applies the @Pipe decorator, and adds a spec file for tests.

ng generate pipe truncate

This produces truncate.pipe.ts and truncate.pipe.spec.ts ready for you to fill in the transform body.

Typing the transform method

For robust pipes, type both the input and the return value precisely. Generics are useful when a pipe operates over collections so callers retain the element type.

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

@Pipe({ name: 'filterBy' })
export class FilterByPipe implements PipeTransform {
  transform<T>(items: T[], predicate: (item: T) => boolean): T[] {
    if (!Array.isArray(items)) return items;
    return items.filter(predicate);
  }
}
Decorator optionTypePurpose
namestringThe identifier used in templates (required)
purebooleanWhether the pipe is pure; defaults to true
standalonebooleanStandalone usage; defaults to true in v17+

Avoid building array-filtering or sorting pipes for production lists. They are convenient but couple presentation to data shaping; prefer transforming arrays with signals or a computed value in the component for predictable performance.

Best practices

  • Implement PipeTransform explicitly so the compiler verifies your transform signature.
  • Keep pipes pure and side-effect-free — return a new value, never mutate the input.
  • Provide sensible default argument values so the pipe works with the bare | name syntax.
  • Type the input and return values precisely, using generics for collection pipes.
  • Scaffold with ng generate pipe to get a consistent class plus test file.
  • Reserve impure pipes for genuinely dynamic data and document why a pipe is impure.
  • Favor a single focused responsibility per pipe rather than one pipe with many modes.
Last updated June 14, 2026
Was this helpful?