Pipes are simple functions used in templates to transform data for display. Angular provides built-in pipes for common transformations and allows you to create custom pipes for specific needs.
What are Pipes?
Pipes take data as input and transform it to a desired output format. They are used in template expressions:
{{ value | pipeName }}
{{ value | pipeName : arg1 : arg2 }}
{{ value | pipe1 | pipe2 | pipe3 }}
Pipes are pure by default, meaning they only execute when their input reference changes. This ensures optimal performance.
Built-in Pipes
Angular provides several built-in pipes:
DatePipe
Format dates:
import { Component } from '@angular/core' ;
import { DatePipe } from '@angular/common' ;
@ Component ({
selector: 'app-date-demo' ,
standalone: true ,
imports: [ DatePipe ],
template: `
<p>{{ today | date }}</p>
<p>{{ today | date:'short' }}</p>
<p>{{ today | date:'medium' }}</p>
<p>{{ today | date:'long' }}</p>
<p>{{ today | date:'full' }}</p>
<p>{{ today | date:'shortDate' }}</p>
<p>{{ today | date:'mediumTime' }}</p>
<p>{{ today | date:'yyyy-MM-dd' }}</p>
<p>{{ today | date:'h:mm a' }}</p>
<p>{{ today | date:'EEEE, MMMM d, y' }}</p>
`
})
export class DateDemoComponent {
today = new Date ();
}
CurrencyPipe
Format currency values:
import { Component } from '@angular/core' ;
import { CurrencyPipe } from '@angular/common' ;
@ Component ({
selector: 'app-currency-demo' ,
standalone: true ,
imports: [ CurrencyPipe ],
template: `
<p>{{ price | currency }}</p>
<p>{{ price | currency:'EUR' }}</p>
<p>{{ price | currency:'GBP':'symbol':'1.2-2' }}</p>
<p>{{ price | currency:'USD':'code' }}</p>
<p>{{ largeAmount | currency:'USD':'symbol-narrow':'1.0-0' }}</p>
`
})
export class CurrencyDemoComponent {
price = 123.456 ;
largeAmount = 1234567.89 ;
}
DecimalPipe
Format numbers:
import { Component } from '@angular/core' ;
import { DecimalPipe } from '@angular/common' ;
@ Component ({
selector: 'app-decimal-demo' ,
standalone: true ,
imports: [ DecimalPipe ],
template: `
<p>{{ pi | number }}</p>
<p>{{ pi | number:'1.2-4' }}</p>
<p>{{ largeNumber | number:'1.0-0' }}</p>
<p>{{ percentage | number:'1.2-2' }}</p>
`
})
export class DecimalDemoComponent {
pi = 3.14159265359 ;
largeNumber = 1234567 ;
percentage = 0.259 ;
}
PercentPipe
Format percentages:
import { Component } from '@angular/core' ;
import { PercentPipe } from '@angular/common' ;
@ Component ({
selector: 'app-percent-demo' ,
standalone: true ,
imports: [ PercentPipe ],
template: `
<p>{{ ratio | percent }}</p>
<p>{{ ratio | percent:'1.2-2' }}</p>
<p>{{ 0.5 | percent }}</p>
`
})
export class PercentDemoComponent {
ratio = 0.259 ;
}
UpperCasePipe and LowerCasePipe
Transform text case:
import { Component } from '@angular/core' ;
import { UpperCasePipe , LowerCasePipe , TitleCasePipe } from '@angular/common' ;
@ Component ({
selector: 'app-case-demo' ,
standalone: true ,
imports: [ UpperCasePipe , LowerCasePipe , TitleCasePipe ],
template: `
<p>{{ text | uppercase }}</p>
<p>{{ text | lowercase }}</p>
<p>{{ text | titlecase }}</p>
`
})
export class CaseDemoComponent {
text = 'hello world' ;
}
JsonPipe
Display objects for debugging:
import { Component } from '@angular/core' ;
import { JsonPipe } from '@angular/common' ;
@ Component ({
selector: 'app-json-demo' ,
standalone: true ,
imports: [ JsonPipe ],
template: `
<pre>{{ user | json }}</pre>
`
})
export class JsonDemoComponent {
user = {
name: 'John Doe' ,
email: 'john@example.com' ,
roles: [ 'admin' , 'user' ]
};
}
SlicePipe
Extract a subset of array or string:
import { Component } from '@angular/core' ;
import { SlicePipe } from '@angular/common' ;
@ Component ({
selector: 'app-slice-demo' ,
standalone: true ,
imports: [ SlicePipe ],
template: `
<p>{{ text | slice:0:5 }}</p>
<ul>
@for (item of items | slice:0:3; track item) {
<li>{{ item }}</li>
}
</ul>
`
})
export class SliceDemoComponent {
text = 'Hello World' ;
items = [ 'Item 1' , 'Item 2' , 'Item 3' , 'Item 4' , 'Item 5' ];
}
AsyncPipe
Automatically subscribe to observables:
import { Component , inject } from '@angular/core' ;
import { AsyncPipe } from '@angular/common' ;
import { HttpClient } from '@angular/common/http' ;
import { Observable } from 'rxjs' ;
interface User {
id : number ;
name : string ;
}
@ Component ({
selector: 'app-async-demo' ,
standalone: true ,
imports: [ AsyncPipe ],
template: `
@if (users$ | async; as users) {
<ul>
@for (user of users; track user.id) {
<li>{{ user.name }}</li>
}
</ul>
} @else {
<p>Loading...</p>
}
`
})
export class AsyncDemoComponent {
private http = inject ( HttpClient );
users$ : Observable < User []> = this . http . get < User []>( '/api/users' );
}
The AsyncPipe automatically unsubscribes when the component is destroyed, preventing memory leaks.
Creating Custom Pipes
Implement the PipeTransform interface:
import { Pipe , PipeTransform } from '@angular/core' ;
@ Pipe ({
name: 'exponential' ,
standalone: true
})
export class ExponentialPipe implements PipeTransform {
transform ( value : number , exponent : number = 1 ) : number {
return Math . pow ( value , exponent );
}
}
// Usage
@ Component ({
selector: 'app-demo' ,
standalone: true ,
imports: [ ExponentialPipe ],
template: `
<p>{{ 2 | exponential }}</p> <!-- 2 -->
<p>{{ 2 | exponential:3 }}</p> <!-- 8 -->
<p>{{ 5 | exponential:2 }}</p> <!-- 25 -->
`
})
export class DemoComponent { }
Pipe with Multiple Arguments
import { Pipe , PipeTransform } from '@angular/core' ;
@ Pipe ({
name: 'truncate' ,
standalone: true
})
export class TruncatePipe implements PipeTransform {
transform ( value : string , limit : number = 10 , suffix : string = '...' ) : string {
if ( ! value ) return '' ;
if ( value . length <= limit ) {
return value ;
}
return value . substring ( 0 , limit ) + suffix ;
}
}
// Usage
@ Component ({
selector: 'app-demo' ,
standalone: true ,
imports: [ TruncatePipe ],
template: `
<p>{{ longText | truncate }}</p>
<p>{{ longText | truncate:20 }}</p>
<p>{{ longText | truncate:15:'...' }}</p>
`
})
export class DemoComponent {
longText = 'This is a very long text that needs truncation' ;
}
Impure Pipes
Pipes that detect all changes (use sparingly):
import { Pipe , PipeTransform } from '@angular/core' ;
@ Pipe ({
name: 'filter' ,
standalone: true ,
pure: false // Impure pipe - executes on every change detection
})
export class FilterPipe implements PipeTransform {
transform ( items : any [], searchText : string , field : string ) : any [] {
if ( ! items || ! searchText ) {
return items ;
}
searchText = searchText . toLowerCase ();
return items . filter ( item => {
return item [ field ]. toLowerCase (). includes ( searchText );
});
}
}
// Usage
@ Component ({
selector: 'app-search' ,
standalone: true ,
imports: [ FilterPipe , FormsModule ],
template: `
<input [(ngModel)]="searchText" placeholder="Search...">
<ul>
@for (user of users | filter:searchText:'name'; track user.id) {
<li>{{ user.name }}</li>
}
</ul>
`
})
export class SearchComponent {
searchText = '' ;
users = [
{ id: 1 , name: 'Alice' },
{ id: 2 , name: 'Bob' },
{ id: 3 , name: 'Charlie' }
];
}
Impure pipes can impact performance because they execute on every change detection cycle. Use them carefully.
Chaining Pipes
Combine multiple pipes:
import { Component } from '@angular/core' ;
import { DatePipe , UpperCasePipe , SlicePipe } from '@angular/common' ;
@ Component ({
selector: 'app-chain-demo' ,
standalone: true ,
imports: [ DatePipe , UpperCasePipe , SlicePipe ],
template: `
<p>{{ today | date:'fullDate' | uppercase }}</p>
<p>{{ text | slice:0:10 | uppercase }}</p>
<p>{{ value | number:'1.2-2' | currency:'USD' }}</p>
`
})
export class ChainDemoComponent {
today = new Date ();
text = 'hello world' ;
value = 123.456 ;
}
Complex Custom Pipe
Pipe with dependency injection:
import { Pipe , PipeTransform , inject } from '@angular/core' ;
import { DecimalPipe } from '@angular/common' ;
@ Pipe ({
name: 'fileSize' ,
standalone: true
})
export class FileSizePipe implements PipeTransform {
private decimalPipe = inject ( DecimalPipe );
transform ( bytes : number , precision : number = 2 ) : string {
if ( bytes === 0 ) return '0 Bytes' ;
const k = 1024 ;
const sizes = [ 'Bytes' , 'KB' , 'MB' , 'GB' , 'TB' ];
const i = Math . floor ( Math . log ( bytes ) / Math . log ( k ));
const value = bytes / Math . pow ( k , i );
const formatted = this . decimalPipe . transform ( value , `1.0- ${ precision } ` );
return ` ${ formatted } ${ sizes [ i ] } ` ;
}
}
// Usage
@ Component ({
selector: 'app-demo' ,
standalone: true ,
imports: [ FileSizePipe ],
template: `
<p>{{ 1024 | fileSize }}</p> <!-- 1.00 KB -->
<p>{{ 1048576 | fileSize }}</p> <!-- 1.00 MB -->
<p>{{ 1234567890 | fileSize:1 }}</p> <!-- 1.1 GB -->
`
})
export class DemoComponent { }
Async Operations in Pipes
import { Pipe , PipeTransform , inject } from '@angular/core' ;
import { HttpClient } from '@angular/common/http' ;
import { Observable } from 'rxjs' ;
import { map } from 'rxjs/operators' ;
@ Pipe ({
name: 'fetchUser' ,
standalone: true
})
export class FetchUserPipe implements PipeTransform {
private http = inject ( HttpClient );
transform ( userId : number ) : Observable < string > {
return this . http . get < any >( `/api/users/ ${ userId } ` ). pipe (
map ( user => user . name )
);
}
}
// Usage with AsyncPipe
@ Component ({
selector: 'app-demo' ,
standalone: true ,
imports: [ FetchUserPipe , AsyncPipe ],
template: `
<p>User: {{ userId | fetchUser | async }}</p>
`
})
export class DemoComponent {
userId = 123 ;
}
Best Practices
Keep Pipes Pure Default pure pipes for better performance
Simple Transforms Keep transformation logic simple
Use AsyncPipe For observables to prevent memory leaks
Avoid Side Effects Pipes should only transform data
Keep pipes pure - Unless you have a specific reason
Avoid complex logic - Move to services if needed
Use meaningful names - Clear and descriptive
Handle null/undefined - Defensive programming
Document parameters - Clear usage examples
Test thoroughly - Pipes are easy to unit test
Next Steps
Templates Learn template syntax and bindings
Directives Extend element behavior