Skip to main content

Pipes

Built-in Pipes

@Component({
template: `
<!-- String pipes -->
<p>{{ 'hello' | uppercase }}</p> <!-- HELLO -->
<p>{{ 'HELLO' | lowercase }}</p> <!-- hello -->
<p>{{ 'hello world' | titlecase }}</p> <!-- Hello World -->

<!-- Number pipes -->
<p>{{ 1234.56 | number:'1.2-2' }}</p> <!-- 1,234.56 -->
<p>{{ 0.259 | percent }}</p> <!-- 26% -->
<p>{{ 1234.56 | currency:'USD' }}</p> <!-- $1,234.56 -->
<p>{{ 1234.56 | currency:'EUR':'symbol':'1.2-2':'fr' }}</p> <!-- 1 234,56 € -->

<!-- Date pipe -->
<p>{{ today | date }}</p> <!-- Jan 1, 2024 -->
<p>{{ today | date:'short' }}</p> <!-- 1/1/24, 12:00 PM -->
<p>{{ today | date:'fullDate' }}</p> <!-- Monday, January 1, 2024 -->
<p>{{ today | date:'yyyy-MM-dd' }}</p> <!-- 2024-01-01 -->

<!-- JSON pipe -->
<pre>{{ user | json }}</pre>

<!-- Slice pipe -->
<p>{{ [1,2,3,4,5] | slice:1:3 }}</p> <!-- [2,3] -->

<!-- Async pipe -->
<p>{{ users$ | async }}</p>

<!-- KeyValue pipe -->
<div *ngFor="let item of object | keyvalue">
{{ item.key }}: {{ item.value }}
</div>
`
})
export class PipesExampleComponent {
today = new Date();
user = { name: 'John', age: 30 };
users$ = this.userService.getUsers();
object = { a: 1, b: 2, c: 3 };
}

Custom Pipe

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

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

// Usage
<p>{{ longText | truncate:20:'...' }}</p>

Pure vs Impure Pipes

// Pure pipe (default) - only runs when input reference changes
@Pipe({
name: 'filter',
standalone: true,
pure: true // default
})
export class FilterPipe implements PipeTransform {
transform(items: any[], searchText: string): any[] {
if (!items || !searchText) return items;
return items.filter(item =>
item.name.toLowerCase().includes(searchText.toLowerCase())
);
}
}

// Impure pipe - runs on every change detection
@Pipe({
name: 'impureFilter',
standalone: true,
pure: false // runs every CD cycle
})
export class ImpureFilterPipe implements PipeTransform {
transform(items: any[], searchText: string): any[] {
return items.filter(item => item.name.includes(searchText));
}
}

Async Pipe

@Component({
template: `
<!-- Subscribes and unsubscribes automatically -->
<div *ngIf="users$ | async as users">
<p *ngFor="let user of users">{{ user.name }}</p>
</div>

<!-- Multiple subscriptions (bad) -->
<div *ngIf="users$ | async">
<p>Count: {{ (users$ | async)?.length }}</p> <!-- Don't do this -->
</div>

<!-- Single subscription (good) -->
<div *ngIf="users$ | async as users">
<p>Count: {{ users.length }}</p>
</div>
`
})
export class UserListComponent {
users$ = this.userService.getUsers();
}

Chaining Pipes

@Component({
template: `
<!-- Apply multiple pipes -->
<p>{{ name | uppercase | slice:0:10 }}</p>

<!-- Date formatting and localization -->
<p>{{ date | date:'fullDate' | uppercase }}</p>

<!-- Number formatting -->
<p>{{ price | currency:'USD' | uppercase }}</p>
`
})

Common Custom Pipes

Filter Pipe

@Pipe({ name: 'filter', standalone: true })
export class FilterPipe implements PipeTransform {
transform(items: any[], field: string, value: any): any[] {
if (!items || !field || value === undefined) return items;
return items.filter(item => item[field] === value);
}
}

// Usage
<div *ngFor="let user of users | filter:'active':true">
{{ user.name }}
</div>

Sort Pipe

@Pipe({ name: 'sort', standalone: true })
export class SortPipe implements PipeTransform {
transform(items: any[], field: string, reverse: boolean = false): any[] {
if (!items || !field) return items;

const sorted = [...items].sort((a, b) => {
if (a[field] < b[field]) return -1;
if (a[field] > b[field]) return 1;
return 0;
});

return reverse ? sorted.reverse() : sorted;
}
}

// Usage
<div *ngFor="let user of users | sort:'name':false">
{{ user.name }}
</div>

Safe HTML Pipe

import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';

@Pipe({ name: 'safeHtml', standalone: true })
export class SafeHtmlPipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {}

transform(html: string): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(html);
}
}

// Usage
<div [innerHTML]="htmlContent | safeHtml"></div>

Time Ago Pipe

@Pipe({ name: 'timeAgo', standalone: true })
export class TimeAgoPipe implements PipeTransform {
transform(value: Date | string): string {
const date = new Date(value);
const now = new Date();
const seconds = Math.floor((now.getTime() - date.getTime()) / 1000);

const intervals = {
year: 31536000,
month: 2592000,
week: 604800,
day: 86400,
hour: 3600,
minute: 60,
second: 1
};

for (const [unit, secondsInUnit] of Object.entries(intervals)) {
const interval = Math.floor(seconds / secondsInUnit);
if (interval >= 1) {
return interval === 1
? `1 ${unit} ago`
: `${interval} ${unit}s ago`;
}
}

return 'just now';
}
}

// Usage
<p>{{ post.createdAt | timeAgo }}</p> <!-- 2 hours ago -->

Pipe with Dependencies

@Pipe({ name: 'translate', standalone: true })
export class TranslatePipe implements PipeTransform {
constructor(private translateService: TranslateService) {}

transform(key: string, params?: any): string {
return this.translateService.instant(key, params);
}
}

// Usage
<p>{{ 'WELCOME_MESSAGE' | translate:{ name: userName } }}</p>

Best Practices

  • Use pure pipes when possible (better performance)
  • Use async pipe for observables (auto-unsubscribe)
  • Avoid impure pipes (they run on every change detection)
  • Don't use pipes for filtering/sorting large lists (use component logic)
  • Keep pipe logic simple and fast
  • Use memoization for expensive computations
  • Prefer multiple async pipes over manual subscriptions