How to Create a Custom Pipe in Angular: Complete Guide with Examples
Create a class implementing PipeTransform, decorate it with @Pipe({name: 'myPipe'}), and use the | operator in templates to format data.
A custom pipe in Angular is the idiomatic way to transform raw data into human-readable formats directly within your templates. According to the angular/angular source code, pipes are pure functions registered in Angular's pipe registry and executed by the Ivy rendering engine through optimized runtime instructions.
What is a Custom Pipe in Angular?
Angular pipes are pure functions that accept an input value and return a transformed value for display. The framework implements pipes as classes decorated with @Pipe that implement the PipeTransform interface. When you use a custom pipe in Angular templates, the Ivy compiler generates calls to ɵɵpipe and ɵɵpipeBindN (found in packages/core/src/render3/pipe.ts) to create and bind pipe instances efficiently.
Creating a Custom Pipe in Angular (Step-by-Step)
Step 1: Implement the PipeTransform Interface
Create a TypeScript class that imports Pipe and PipeTransform from @angular/core. Implement the required transform method with your formatting logic:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'truncate',
// pure: true // default behavior; can be omitted
})
export class TruncatePipe implements PipeTransform {
transform(value: string, length: number, symbol: string = '…'): string {
if (!value) return '';
return value.split(' ').slice(0, length).join(' ') + symbol;
}
}
This example follows the reference implementation in packages/examples/core/ts/pipes/truncate.ts. The transform method signature determines what arguments you can pass from the template.
Step 2: Register the Pipe in Your Application
You must register the custom pipe so Angular's runtime can resolve it by name. Choose one of these approaches based on your architecture:
Option A: NgModule declarations
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { TruncatePipe } from './pipes/truncate';
import { AppComponent } from './app.component';
@NgModule({
declarations: [TruncatePipe, AppComponent],
imports: [BrowserModule],
bootstrap: [AppComponent],
})
export class AppModule {}
Option B: Standalone component imports
import { Component } from '@angular/core';
import { TruncatePipe } from './pipes/truncate';
@Component({
selector: 'app-product-card',
standalone: true,
imports: [TruncatePipe],
template: `<p>{{ description | truncate:15:'...' }}</p>`
})
export class ProductCardComponent {
description = 'This is a very long product description that needs truncation';
}
Angular resolves the pipe at runtime using getPipeDef (located in packages/core/src/render3/pipe.ts). If the pipe isn't declared or imported, the framework throws the PIPE_NOT_FOUND error.
Step 3: Use the Pipe in Templates
Apply your custom pipe using the pipe operator (|) followed by optional colon-separated arguments:
<!-- Single argument -->
<p>{{ article.title | truncate:8 }}</p>
<!-- Multiple arguments -->
<p>{{ product.description | truncate:10:'...' }}</p>
<!-- Chaining with built-in pipes -->
<p>{{ user.bio | truncate:20 | uppercase }}</p>
The template compiler generates Ivy instructions that map to ɵɵpipeBindN calls (e.g., ɵɵpipeBind2 for two arguments), ensuring the transform method executes only when inputs change.
Pure vs. Impure Pipes in Angular
Angular pipes operate in two modes controlled by the pure property in the @Pipe decorator:
Pure pipes (default: pure: true)
- Execute only when the input value or arguments change by reference
- Most performant choice for deterministic formatting (dates, numbers, string manipulation)
- Used in the truncate example above
Impure pipes (pure: false)
- Execute on every change detection cycle
- Required for data that mutates outside Angular's knowledge (e.g., async data streams, global state)
- Use sparingly to avoid performance degradation
Set impure mode only when necessary:
@Pipe({
name: 'expensiveTransform',
pure: false
})
export class ExpensiveTransformPipe implements PipeTransform {
transform(value: any): any {
// This runs every change detection cycle
return heavyComputation(value);
}
}
Complete Custom Pipe Example: Truncate Pipe
Here is the full implementation based on the Angular source code example in packages/examples/core/ts/pipes/truncate.ts:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'truncate',
})
export class TruncatePipe implements PipeTransform {
/**
* Truncates a string to a specified number of words.
* @param value The string to truncate
* @param length Maximum number of words to keep
* @param symbol Suffix to append (defaults to '…')
*/
transform(value: string, length: number, symbol: string = '…'): string {
if (!value || length <= 0) return '';
const words = value.split(' ');
if (words.length <= length) return value;
return words.slice(0, length).join(' ') + symbol;
}
}
Usage in component:
@Component({
selector: 'app-article',
standalone: true,
imports: [TruncatePipe],
template: `
<h2>{{ article.title }}</h2>
<p>{{ article.body | truncate:50:'... (read more)' }}</p>
`
})
export class ArticleComponent {
article = {
title: 'Angular Pipes Guide',
body: 'This comprehensive guide covers everything you need to know about creating and using custom pipes in your Angular applications...'
};
}
How Angular Executes Custom Pipes (Ivy Runtime)
Understanding the runtime mechanics helps optimize pipe usage. When Angular compiles a template containing {{ value | myPipe:arg }}, the Ivy compiler generates instructions that call into packages/core/src/render3/pipe.ts:
- Instantiation:
ɵɵpipe(index, pipeName)retrieves the pipe definition (PipeDef) from the registry and creates an instance - Binding:
ɵɵpipeBindN(index, slotOffset, ...args)invokes thetransformmethod with current values - Change detection: The runtime compares current and previous values; if equal (and pipe is pure), it skips re-execution
The getPipeDef function performs the runtime lookup. If the pipe name isn't found in the registry, Angular throws the PIPE_NOT_FOUND error.
Performance Best Practices for Custom Pipes
- Keep pipes pure unless you specifically need continuous evaluation. Pure pipes leverage Angular's change detection optimizations and only run when inputs change.
- Avoid heavy computations inside
transform. Pipes should execute synchronously and quickly. For expensive operations, consider pre-computing values in components or using memoization. - Memoize manually if your pipe performs complex calculations on stable inputs. Cache results in a
Mapor similar structure keyed by input arguments. - Prefer standalone pipes when using standalone components to enable tree-shaking and better code organization.
- Type strictly by implementing
PipeTransformwith explicit parameter and return types to catch errors at compile time.
Summary
- Create a custom pipe in Angular by implementing
PipeTransformand decorating the class with@Pipe({name: 'yourPipe'}). - Register the pipe in an NgModule's
declarationsarray or import it directly into standalone components. - Apply pipes in templates using the
|operator with optional arguments separated by colons. - Choose pure pipes (default) for optimal performance, reserving impure pipes (
pure: false) for data that changes outside Angular's change detection. - Reference the Angular source in
packages/core/src/render3/pipe.tsfor runtime mechanics andpackages/examples/core/ts/pipes/truncate.tsfor implementation patterns.
Frequently Asked Questions
How do I pass multiple arguments to a custom pipe in Angular?
Separate each argument with a colon in the template expression. For example: {{ value | myPipe:arg1:arg2:arg3 }}. In your PipeTransform implementation, add corresponding parameters to the transform method: transform(value: any, arg1: any, arg2: any, arg3: any): any.
Why is my custom pipe not working or throwing "pipe not found" errors?
Angular resolves pipes at runtime using the getPipeDef function in packages/core/src/render3/pipe.ts. If you see the PIPE_NOT_FOUND error, you likely forgot to declare the pipe in your NgModule's declarations array or import it in your standalone component's imports array. Verify the pipe name in the @Pipe decorator matches exactly what you use in the template.
What is the difference between pure and impure pipes in Angular?
Pure pipes (the default) execute only when Angular detects a change in the input value or arguments by reference comparison. Impure pipes (pure: false in the decorator configuration) execute during every change detection cycle. Use pure pipes for deterministic formatting like dates or strings. Reserve impure pipes for data that mutates outside Angular's knowledge, such as continuously updating timestamps or external data streams.
Can I use custom pipes with standalone components in Angular?
Yes, standalone components can import pipes directly without declaring them in an NgModule. Import your custom pipe class into the component's imports array in the @Component decorator. This approach enables better tree-shaking and aligns with Angular's modern standalone API. The pipe registration still flows through the same runtime mechanisms in packages/core/src/render3/pipe.ts, but the compilation context is handled at the component level rather than the module level.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →