Today, let’s dive into Angular Pipes, a super handy tool in Angular development for transforming and formatting data effortlessly. Whether you’re formatting dates, handling currencies, filtering lists, or tackling complex data transformations, Pipes keep your template code clean and readable.
What Are Angular Pipes?
Pipes in Angular are tools for transforming data in templates. Think of them as “filters” that take raw data and output it in your desired format. For example, turning 1234.567 into $1,234.57 or 2025-07-02 into July 2, 2025 is a breeze with Pipes. Their key features include:
- Declarative: Used directly in templates for concise code.
- Reusable: A single Pipe can be used across multiple components.
- Built-in and Custom: Angular provides a set of built-in Pipes, and you can create your own.
Pipes are applied in templates with the syntax {{ value | pipeName:arg1:arg2 }}, where | is the Pipe operator, followed by the Pipe name and optional arguments. We’ll start with Angular’s built-in Pipes and progress to custom Pipes and advanced scenarios.
Environment Setup
To use Pipes, set up an Angular development environment with Node.js (18.x recommended) and Angular CLI (version 18.x at the time of writing).
Create a new project:
npm install -g @angular/cli
ng new angular-pipes-demo
cd angular-pipes-demo
Choose default settings (CSS, no SSR). The project structure looks like this:
angular-pipes-demo/
├── src/
│ ├── app/
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ ├── main.ts
│ ├── index.html
├── angular.json
├── package.json
Run ng serve and visit localhost:4200 to see the default page. We’ll experiment with Pipes in app.component.
Built-in Pipes: Ready-to-Use Tools
Angular provides a range of built-in Pipes for common data transformation tasks, such as formatting dates, numbers, and strings. Let’s explore a few popular ones.
DatePipe: Formatting Dates
DatePipe transforms date objects or strings into specified formats. Update app.component.ts:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<h1>DatePipe Demo</h1>
<p>Today: {{ today | date }}</p>
<p>Short: {{ today | date:'short' }}</p>
<p>Custom: {{ today | date:'MMMM d, yyyy' }}</p>
`,
})
export class AppComponent {
today = new Date();
}
Run ng serve. The page displays:
Today: Jul 2, 2025Short: 7/2/25, 12:00 AMCustom: July 2, 2025
date accepts format strings like short, medium, full, or custom formats (MMMM d, yyyy). Try dynamic dates:
template: `
<input type="date" [(ngModel)]="selectedDate">
<p>Formatted: {{ selectedDate | date:'fullDate' }}</p>
`
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-root',
template: `...`,
standalone: true,
imports: [FormsModule]
})
export class AppComponent {
selectedDate = new Date();
}
Select a date, and the page updates with formats like “Wednesday, July 2, 2025”.
CurrencyPipe: Formatting Currency
CurrencyPipe converts numbers into currency formats, supporting various symbols and locales. Update app.component.ts:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<h1>CurrencyPipe Demo</h1>
<p>USD: {{ price | currency }}</p>
<p>EUR: {{ price | currency:'EUR' }}</p>
<p>Custom: {{ price | currency:'JPY':'symbol':'1.0-0' }}</p>
`,
})
export class AppComponent {
price = 1234.567;
}
Output:
USD: $1,234.57EUR: €1,234.57Custom: ¥1,235
currency takes three parameters:
- Currency code (
USD,EUR,JPY). - Display type (
symbol,$,code). - Digit format (
1.0-0for 1 integer digit, 0 decimal digits).
Try dynamic input:
template: `
<input type="number" [(ngModel)]="price">
<p>{{ price | currency:'USD':'symbol' }}</p>
`
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-root',
template: `...`,
standalone: true,
imports: [FormsModule]
})
export class AppComponent {
price = 1234.567;
}
Enter a price, and it displays formatted in real-time.
DecimalPipe: Number Formatting
DecimalPipe controls decimal places and thousand separators:
@Component({
selector: 'app-root',
template: `
<h1>DecimalPipe Demo</h1>
<p>Default: {{ number | number }}</p>
<p>Two decimals: {{ number | number:'1.2-2' }}</p>
<p>No fraction: {{ number | number:'1.0-0' }}</p>
`,
})
export class AppComponent {
number = 1234.5678;
}
Output:
Default: 1,234.568Two decimals: 1,234.57No fraction: 1,235
number:'1.2-2' means:
-
1: Minimum 1 integer digit. -
2-2: Exactly 2 decimal digits.
PercentPipe: Percentage Formatting
PercentPipe converts decimals to percentages:
@Component({
selector: 'app-root',
template: `
<h1>PercentPipe Demo</h1>
<p>Default: {{ ratio | percent }}</p>
<p>Three decimals: {{ ratio | percent:'1.3-3' }}</p>
`,
})
export class AppComponent {
ratio = 0.456;
}
Output:
Default: 46%Three decimals: 45.600%
JsonPipe: Debugging Objects
JsonPipe converts objects to JSON strings, great for debugging:
@Component({
selector: 'app-root',
template: `
<h1>JsonPipe Demo</h1>
<pre>{{ user | json }}</pre>
`,
})
export class AppComponent {
user = { name: 'Alice', age: 30, city: 'New York' };
}
Output:
{
"name": "Alice",
"age": 30,
"city": "New York"
}
SlicePipe: Slicing Arrays or Strings
SlicePipe extracts parts of arrays or strings:
@Component({
selector: 'app-root',
template: `
<h1>SlicePipe Demo</h1>
<p>String: {{ text | slice:0:5 }}</p>
<ul>
<li *ngFor="let item of items | slice:1:3">{{ item }}</li>
</ul>
`,
})
export class AppComponent {
text = 'Hello, Angular!';
items = ['Apple', 'Banana', 'Cherry', 'Date'];
}
Output:
String: Hello- List:
Banana,Cherry
slice:0:5 extracts from index 0 to 5 (exclusive), applicable to both arrays and strings.
Chained Pipes
Pipes can be chained, passing the output of one Pipe to the next. Try converting a string to uppercase and slicing:
@Component({
selector: 'app-root',
template: `
<h1>Chained Pipes</h1>
<p>{{ text | uppercase | slice:0:5 }}</p>
`,
})
export class AppComponent {
text = 'hello, angular!';
}
Output: HELLO
uppercase converts to HELLO, ANGULAR!, then slice:0:5 extracts HELLO. Chained Pipes simplify complex transformations.
Custom Pipes: Handling Complex Logic
Built-in Pipes are great, but real projects often need custom logic, like filtering lists or formatting specific data. Let’s create custom Pipes.
Creating a Simple Custom Pipe
Generate a Pipe:
ng generate pipe filter
This creates app/filter.pipe.ts:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'filter',
standalone: true
})
export class FilterPipe implements PipeTransform {
transform(items: any[], term: string): any[] {
if (!items || !term) return items;
return items.filter(item => item.toLowerCase().includes(term.toLowerCase()));
}
}
Use in app.component.ts:
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { FilterPipe } from './filter.pipe';
@Component({
selector: 'app-root',
template: `
<h1>Filter Pipe</h1>
<input type="text" [(ngModel)]="searchTerm">
<ul>
<li *ngFor="let item of items | filter:searchTerm">{{ item }}</li>
</ul>
`,
standalone: true,
imports: [FormsModule, FilterPipe]
})
export class AppComponent {
searchTerm = '';
items = ['Apple', 'Banana', 'Cherry', 'Date'];
}
Enter “ap” to filter the list to Apple. The filter Pipe dynamically filters the array based on input.
Custom Pipe with Parameters
Update filter.pipe.ts to support case sensitivity:
@Pipe({
name: 'filter',
standalone: true
})
export class FilterPipe implements PipeTransform {
transform(items: any[], term: string, caseSensitive: boolean = false): any[] {
if (!items || !term) return items;
return items.filter(item => {
const search = caseSensitive ? term : term.toLowerCase();
const value = caseSensitive ? item : item.toLowerCase();
return value.includes(search);
});
}
}
Update template:
template: `
<h1>Filter Pipe</h1>
<input type="text" [(ngModel)]="searchTerm">
<label><input type="checkbox" [(ngModel)]="caseSensitive"> Case Sensitive</label>
<ul>
<li *ngFor="let item of items | filter:searchTerm:caseSensitive">{{ item }}</li>
</ul>
`
export class AppComponent {
searchTerm = '';
caseSensitive = false;
items = ['Apple', 'banana', 'Cherry', 'Date'];
}
Check “Case Sensitive” and enter “Apple” to match only Apple. Unchecked, it ignores case.
Pure vs. Impure Pipes
By default, Pipes are “pure,” only running when inputs change. For dynamic data (e.g., array mutations), use an “impure” Pipe. Update filter.pipe.ts:
@Pipe({
name: 'filter',
standalone: true,
pure: false
})
export class FilterPipe implements PipeTransform {
transform(items: any[], term: string): any[] {
console.log('FilterPipe triggered');
if (!items || !term) return items;
return items.filter(item => item.toLowerCase().includes(term.toLowerCase()));
}
}
@Component({
selector: 'app-root',
template: `
<h1>Impure Pipe</h1>
<input type="text" [(ngModel)]="searchTerm">
<button (click)="addItem()">Add Item</button>
<ul>
<li *ngFor="let item of items | filter:searchTerm">{{ item }}</li>
</ul>
`,
standalone: true,
imports: [FormsModule, FilterPipe]
})
export class AppComponent {
searchTerm = '';
items = ['Apple', 'Banana'];
addItem() {
this.items.push('New Item');
}
}
Click “Add Item” to mutate items, triggering the filter Pipe (console logs confirm). Pure Pipes don’t re-run for mutations, ideal for static data; impure Pipes handle dynamic changes but have higher performance costs.
Complex Scenario: Formatting Complex Objects
Create a Pipe to format user objects. Generate user-format.pipe.ts:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'userFormat',
standalone: true
})
export class UserFormatPipe implements PipeTransform {
transform(user: { name: string, age: number, city: string }, format: 'short' | 'full' = 'short'): string {
if (!user) return '';
if (format === 'short') {
return `${user.name}, ${user.age}`;
}
return `${user.name}, ${user.age} years old, from ${user.city}`;
}
}
Use in app.component.ts:
import { Component } from '@angular/core';
import { UserFormatPipe } from './user-format.pipe';
@Component({
selector: 'app-root',
template: `
<h1>User Format Pipe</h1>
<p>Short: {{ user | userFormat }}</p>
<p>Full: {{ user | userFormat:'full' }}</p>
`,
standalone: true,
imports: [UserFormatPipe]
})
export class AppComponent {
user = { name: 'Alice', age: 30, city: 'New York' };
}
Output:
Short: Alice, 30Full: Alice, 30 years old, from New York
Async Data: AsyncPipe
AsyncPipe handles Observables or Promises, automatically subscribing and unsubscribing. Update app.component.ts:
import { Component } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';
@Component({
selector: 'app-root',
template: `
<h1>AsyncPipe Demo</h1>
<p>Data: {{ data$ | async }}</p>
`,
standalone: true,
imports: [AsyncPipe]
})
export class AppComponent {
data$ = of('Hello from Observable!').pipe(delay(2000));
}
After 2 seconds, it displays “Hello from Observable!”. AsyncPipe subscribes to data$ and unsubscribes on component destruction, preventing memory leaks.
Try a Promise:
@Component({
selector: 'app-root',
template: `
<h1>AsyncPipe with Promise</h1>
<p>Data: {{ promise | async }}</p>
`,
standalone: true,
imports: [AsyncPipe]
})
export class AppComponent {
promise = new Promise(resolve => {
setTimeout(() => resolve('Hello from Promise!'), 2000);
});
}
Similar effect: displays the result after 2 seconds.
With HTTP Requests
Use HttpClient for data fetching:
ng add @angular/common
Update app.component.ts:
import { Component } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-root',
template: `
<h1>AsyncPipe with HTTP</h1>
<ul>
<li *ngFor="let user of users$ | async">{{ user.name }}</li>
</ul>
`,
standalone: true,
imports: [AsyncPipe]
})
export class AppComponent {
users$ = this.http.get('https://jsonplaceholder.typicode.com/users');
constructor(private http: HttpClient) {}
}
Run it to display a user list. AsyncPipe subscribes to the HTTP Observable and renders the data.
Combining NgFor and Pipes
Pipes often pair with *ngFor for dynamic lists. Combine filter and AsyncPipe:
import { Component } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { FilterPipe } from './filter.pipe';
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';
@Component({
selector: 'app-root',
template: `
<h1>Filter with AsyncPipe</h1>
<input type="text" [(ngModel)]="searchTerm">
<ul>
<li *ngFor="let item of items$ | async | filter:searchTerm">{{ item }}</li>
</ul>
`,
standalone: true,
imports: [AsyncPipe, FormsModule, FilterPipe]
})
export class AppComponent {
searchTerm = '';
items$ = of(['Apple', 'Banana', 'Cherry', 'Date']).pipe(delay(1000));
}
After 1 second, the list loads. Enter “ap” to filter to Apple. AsyncPipe handles the Observable, and filter dynamically filters the list.
Internationalization (i18n) with Pipes
Pipes are great for internationalization, as DatePipe and CurrencyPipe support locale settings. Update app.component.ts:
@Component({
selector: 'app-root',
template: `
<h1>i18n with Pipes</h1>
<select [(ngModel)]="locale">
<option value="en-US">English (US)</option>
<option value="fr-FR">French</option>
</select>
<p>Date: {{ today | date:'fullDate':undefined:locale }}</p>
<p>Currency: {{ price | currency:'USD':'symbol':undefined:locale }}</p>
`,
standalone: true,
imports: [FormsModule]
})
export class AppComponent {
today = new Date();
price = 1234.567;
locale = 'en-US';
}
Switch to French: the date shows “mercredi 2 juillet 2025”, and currency shows “1 234,57 $US”. The locale parameter dynamically adjusts formatting.
Performance: Pure Pipes and Change Detection
Pure Pipes run only when inputs change, optimizing performance. Update user-format.pipe.ts as a pure Pipe:
@Pipe({
name: 'userFormat',
standalone: true,
pure: true
})
export class UserFormatPipe implements PipeTransform {
transform(user: { name: string, age: number }, format: 'short' | 'full' = 'short'): string {
console.log('UserFormatPipe triggered');
return format === 'short' ? `${user.name}, ${user.age}` : `${user.name}, ${user.age} years old`;
}
}
@Component({
selector: 'app-root',
template: `
<h1>Pure Pipe</h1>
<button (click)="update()">Update</button>
<p>{{ user | userFormat:'full' }}</p>
`,
standalone: true,
imports: [UserFormatPipe]
})
export class AppComponent {
user = { name: 'Alice', age: 30 };
update() {
this.user = { ...this.user }; // Shallow copy
}
}
Click the button: the Pipe doesn’t re-run (no console logs) because the user reference hasn’t changed. Set pure: false to make it impure, and it runs on every click.
Complex Scenario: Chained Custom Pipes
Create a Pipe to convert numbers to words, then uppercase them:
ng generate pipe numberToWords
number-to-words.pipe.ts:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'numberToWords',
standalone: true
})
export class NumberToWordsPipe implements PipeTransform {
private units = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'];
transform(value: number): string {
if (value < 0 || value > 9) return 'Invalid';
return this.units[value];
}
}
Use in template:
@Component({
selector: 'app-root',
template: `
<h1>Chained Custom Pipes</h1>
<input type="number" [(ngModel)]="number">
<p>{{ number | numberToWords | uppercase }}</p>
`,
standalone: true,
imports: [FormsModule, NumberToWordsPipe]
})
export class AppComponent {
number = 5;
}
Enter 5 to display FIVE. numberToWords converts 5 to five, and uppercase transforms it to FIVE.
Conclusion (Technical Details)
Angular Pipes are powerful for data transformation. Built-in Pipes (DatePipe, CurrencyPipe, etc.) handle common formats, custom Pipes tackle complex logic, and AsyncPipe manages async data. The examples showed:
- Built-in Pipes for dates, currencies, numbers, percentages, JSON, and slicing.
- Custom Pipes for list filtering and object formatting.
- Pure/impure Pipes and their performance impact.
- Async data and internationalization support.
- Chained Pipes and dynamic interactions.
Run these examples, experiment with formats and filtering, and experience the flexibility of Angular Pipes!
Top comments (0)