Pipes in Angular
Pipes are a powerful feature in Angular that allow you to transform data directly in your templates. Instead of cluttering your component logic with formatting code, you declare how data should look in the template using the pipe operator |.
Angular pipes are pure functions — they take an input value, optionally accept parameters, and return a transformed output. They are lazy: Angular only re-evaluates a pipe when its input changes.
What Is a Pipe?
A pipe is used inside Angular template expressions with the | (pipe) character. The syntax is:
{{ value | pipeName }}
{{ value | pipeName: arg1 : arg2 }}
For example, to display a date in a human-readable format:
<!-- Without pipe -->
<p>{{ user.createdAt }}</p>
<!-- Output: 2024-01-15T09:30:00.000Z -->
<!-- With pipe -->
<p>{{ user.createdAt | date }}</p>
<!-- Output: Jan 15, 2024 -->
<!-- With pipe parameters -->
<p>{{ user.createdAt | date: 'fullDate' }}</p>
<!-- Output: Monday, January 15, 2024 -->Why Use Pipes?
Pipes keep your component class clean by moving display logic to the template. Without pipes you would have to write formatting methods in every component that needs them. Compare these two approaches:
// Without pipes — cluttered component
@Component({
template: `<p>{{ getFormattedPrice(product.price) }}</p>`
})
export class ProductComponent {
getFormattedPrice(price: number): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(price);
}
}
// With pipes — clean and reusable
@Component({
template: `<p>{{ product.price | currency: 'USD' }}</p>`
})
export class ProductComponent {}Pure vs Impure Pipes
Every Angular pipe is either pure (default) or impure. This distinction is critical for performance.
Pure pipes are only re-executed when Angular detects a pure change to the input — meaning a new primitive value, or a new object/array reference. Angular can safely cache the result.
Impure pipes run on every change detection cycle, regardless of whether the input changed. Use them sparingly.
Aspect | Pure Pipe | Impure Pipe |
|---|---|---|
When it runs | Only when input reference changes | Every change detection cycle |
Performance | Fast — Angular caches results | Slow — runs very frequently |
Mutable data | Will NOT detect in-place array/object mutation | Will detect all mutations |
Default | Yes (pure: true) | No (pure: false) |
Example | DatePipe, CurrencyPipe | AsyncPipe, JsonPipe |
Chaining Pipes
You can chain multiple pipes together. They are applied left to right:
<!-- Chain: lowercase, then titlecase -->
<p>{{ 'HELLO WORLD' | lowercase | titlecase }}</p>
<!-- Output: Hello World -->
<!-- Chain: date then uppercase -->
<p>{{ today | date: 'MMMM' | uppercase }}</p>
<!-- Output: JANUARY -->
<!-- In @for control flow -->
@for (name of names | slice: 0 : 5; track name) {
<li>{{ name | titlecase }}</li>
}Pipes with Parameters
Pipes accept parameters after the pipe name, separated by colons. Multiple parameters use additional colons:
<!-- Single parameter -->
{{ 3.14159 | number: '1.2-2' }}
<!-- Output: 3.14 -->
<!-- Multiple parameters: slice(start, end) -->
{{ [1, 2, 3, 4, 5] | slice: 1 : 4 }}
<!-- Output: 2,3,4 -->
<!-- Currency with locale and symbol -->
{{ 1234.56 | currency: 'EUR' : 'symbol' : '1.0-0' }}
<!-- Output: EUR1,235 -->
<!-- Date with format and timezone -->
{{ launchDate | date: 'medium' : '+0200' }}Using Pipes in Component Classes
Pipes are not limited to templates. You can inject and use them directly in your TypeScript component logic:
import { Component } from '@angular/core';
import { DatePipe } from '@angular/common';
@Component({
selector: 'app-report',
standalone: true,
imports: [DatePipe],
providers: [DatePipe],
template: `<p>{{ formattedDate }}</p>`,
})
export class ReportComponent {
formattedDate: string;
constructor(private datePipe: DatePipe) {
const now = new Date();
this.formattedDate = this.datePipe.transform(now, 'yyyy-MM-dd') ?? '';
}
}Pipes in Standalone vs Module-Based Apps
In standalone components (Angular 14+), import the pipe directly in the component's imports array:
// Standalone component
import { Component } from '@angular/core';
import { CurrencyPipe, DatePipe, UpperCasePipe } from '@angular/common';
@Component({
selector: 'app-product',
standalone: true,
imports: [CurrencyPipe, DatePipe, UpperCasePipe],
template: `
<h2>{{ product.name | uppercase }}</h2>
<p>{{ product.price | currency }}</p>
<small>Listed: {{ product.listedAt | date: 'shortDate' }}</small>
`,
})
export class ProductComponent {
product = { name: 'laptop', price: 999.99, listedAt: new Date() };
}// Module-based app — import CommonModule (includes all built-in pipes)
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProductComponent } from './product.component';
@NgModule({
declarations: [ProductComponent],
imports: [CommonModule], // gives access to all built-in pipes
})
export class ProductModule {}Common Pitfalls
Forgetting to import the pipe in standalone components — you'll see no transformation or a runtime error
Mutating an array in-place and expecting a pure pipe to re-run — Angular will not detect the change; create a new array instead
Using impure pipes for heavy computations — they run on every change detection cycle and can tank performance
Chaining too many pipes — each transformation creates a new value; consider pre-formatting in the component for complex logic
Passing wrong argument types — e.g. passing a plain string to DatePipe when it expects a Date object, number, or ISO string
Summary
Pipes are one of Angular's most elegant features:
- Transform data in the template without polluting component logic
- Chainable with
|and parameterizable with: - Pure by default — efficient and cacheable
- Built-in pipes (DatePipe, CurrencyPipe, etc.) cover most needs
- Custom pipes are easy to write for domain-specific transformations
Next up: explore the full set of Built-in Pipes Angular provides out of the box.