A pipe transforms a value in the template using the | operator. Pipes do not mutate the original value — they return a new transformed one.
<p>{{ username | uppercase }}</p>
<p>{{ dueDate | date:'mediumDate' }}</p>
1. Built-in pipes
All built-in pipes come from @angular/common. In standalone components, import each pipe individually rather than the entire CommonModule.
import { DatePipe, UpperCasePipe, CurrencyPipe } from '@angular/common';
@Component({
imports: [DatePipe, UpperCasePipe, CurrencyPipe],
...
})
| Pipe | Example | Output |
|---|---|---|
uppercase |
`'hello' \ | uppercase` |
lowercase |
`'HELLO' \ | lowercase` |
titlecase |
`'hello world' \ | titlecase` |
date |
`date \ | date:'mediumDate'` |
number |
`3.14159 \ | number:'1.0-2'` |
currency |
`9.9 \ | currency:'USD'` |
percent |
`0.85 \ | percent` |
slice |
`'Angular' \ | slice:0:3` |
json |
`obj \ | json` |
keyvalue |
`obj \ | keyvalue` |
async |
`obs$ \ | async` |
Pipe parameters
Pass parameters after the pipe name with :. Chain multiple parameters with more colons.
<!-- date format string -->
<p>{{ dueDate | date:'yyyy-MM-dd' }}</p>
<!-- number: minIntegers.minFractionDigits-maxFractionDigits -->
<p>{{ 3.14159 | number:'1.0-2' }}</p> <!-- 3.14 -->
<!-- currency: currencyCode, display, digitsInfo -->
<p>{{ 9.9 | currency:'EUR':'symbol':'1.2-2' }}</p> <!-- €9.90 -->
Chaining pipes
Apply multiple pipes left to right:
<p>{{ username | slice:0:10 | uppercase }}</p>
async pipe
AsyncPipe subscribes to an Observable or Promise and returns its latest emitted value. It automatically unsubscribes when the component is destroyed.
@if (users$ | async; as users) {
@for (user of users; track user.id) {
<li>{{ user.name }}</li>
}
}
In modern Angular,
toSignal()is often a cleaner alternative — it converts the Observable to a signal so you can read the value withusers()in the template without needing theasyncpipe.
2. Custom pipes
Create a pipe with the @Pipe decorator and implement the PipeTransform interface. The transform() method receives the input value as its first argument, followed by any additional parameters you declare.
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'truncate',
standalone: true,
})
export class TruncatePipe implements PipeTransform {
transform(value: string, limit: number = 20, ellipsis: string = '…'): string {
return value.length > limit ? value.slice(0, limit) + ellipsis : value;
}
}
Import it in the component and use it in the template. Extra arguments map to the additional parameters in transform(), in order:
@Component({
imports: [TruncatePipe],
...
})
<p>{{ description | truncate }}</p> <!-- limit=20, ellipsis='…' -->
<p>{{ description | truncate:50 }}</p> <!-- limit=50, ellipsis='…' -->
<p>{{ description | truncate:50:'...' }}</p> <!-- limit=50, ellipsis='...' -->
3. Pure vs impure pipes
By default every pipe is pure — Angular only calls transform() when the input value or parameters change by reference. If you mutate a property inside an object or push an item into an array without replacing the reference, a pure pipe will not re-run, because the object or array reference itself has not changed.
Set pure: false to make a pipe impure — Angular then calls transform() on every change detection cycle, regardless of whether the reference changed.
@Pipe({
name: 'filterActive',
standalone: true,
pure: false,
})
export class FilterActivePipe implements PipeTransform {
transform(items: Item[]): Item[] {
return items.filter(item => item.active);
}
}
With pure: true (the default), pushing a new item into items would not trigger the pipe. With pure: false, it runs on every cycle and picks up the change — but also runs constantly, which can hurt performance on large lists.
Prefer component logic over impure pipes. Filter or sort in the component class and expose the result as a property or computed signal. This gives you control over when the work runs and avoids the performance cost of running on every change detection cycle.
Top comments (0)