Introduction to Pipes
In Angular applications, we often need to transform data before displaying it in our templates. For example, we might want to:
- Format dates in a user-friendly way
- Convert text to uppercase or lowercase
- Display numbers with specific decimal places
- Filter arrays based on certain criteria
- Convert currencies for international users
Angular pipes provide a clean, reusable way to handle these transformations directly in your templates, without cluttering your component logic.
Real-World Analogy: Cooking Filters
Think of pipes like kitchen tools that transform ingredients. Just as a pasta strainer transforms boiling water and uncooked pasta into drained, ready-to-serve pasta, Angular pipes transform raw data into display-ready information. Different recipes require different tools (e.g., colanders, sieves, graters), just as different data needs different pipes.
Pipe Syntax in Templates
Using pipes in Angular templates is straightforward. You use the pipe symbol | (vertical bar) to apply
a pipe to a value:
{{ value | pipeName }}
Many pipes can accept parameters, which are specified after a colon:
{{ value | pipeName:parameter1:parameter2 }}
You can also chain multiple pipes, with each pipe taking the output of the previous pipe as its input:
{{ value | pipe1 | pipe2 | pipe3 }}
Simple Example
<div>
<p>Original: {{ product.name }}</p>
<p>Uppercase: {{ product.name | uppercase }}</p>
<p>Price: {{ product.price | currency:'USD' }}</p>
<p>Purchase date: {{ product.purchaseDate | date:'medium' }}</p>
</div>
This example demonstrates using pipes to:
- Convert text to uppercase
- Format a number as a US currency
- Format a date in medium length format
Built-in Pipes
Angular provides several built-in pipes that cover many common data transformation needs:
uppercase, lowercase,
titlecase] A --> C[Number Pipes
number, percent,
currency] A --> D[Date & Time
date] A --> E[Object Pipes
json, keyvalue] A --> F[Array Pipes
slice] A --> G[Other
async, i18n]
Text Transformation Pipes
<div>
<p>Original: {{ 'angular pipes' }}</p>
<p>uppercase: {{ 'angular pipes' | uppercase }}</p> <!-- ANGULAR PIPES -->
<p>lowercase: {{ 'Angular Pipes' | lowercase }}</p> <!-- angular pipes -->
<p>titlecase: {{ 'angular pipes example' | titlecase }}</p> <!-- Angular Pipes Example -->
</div>
Number Formatting Pipes
<div>
<p>Original: {{ 1234.5678 }}</p>
<!-- number:'minIntegerDigits.minFractionDigits-maxFractionDigits' -->
<p>number: {{ 1234.5678 | number }}</p> <!-- 1,234.568 -->
<p>number:'1.2-2': {{ 1234.5678 | number:'1.2-2' }}</p> <!-- 1,234.57 -->
<p>percent: {{ 0.8456 | percent }}</p> <!-- 84.56% -->
<p>percent:'2.2-2': {{ 0.8456 | percent:'2.2-2' }}</p> <!-- 84.56% -->
<p>currency: {{ 49.99 | currency }}</p> <!-- $49.99 -->
<p>currency:'EUR': {{ 49.99 | currency:'EUR' }}</p> <!-- €49.99 -->
<p>currency:'JPY':'code': {{ 4999 | currency:'JPY':'code' }}</p> <!-- JPY 4,999 -->
</div>
Date Formatting Pipe
The date pipe is one of the most versatile and commonly used pipes in Angular applications.
<div>
<!-- Assume currentDate = new Date() -->
<p>Full date: {{ currentDate | date:'fullDate' }}</p> <!-- Monday, June 15, 2023 -->
<p>Short date: {{ currentDate | date:'shortDate' }}</p> <!-- 6/15/23 -->
<p>Medium date: {{ currentDate | date:'mediumDate' }}</p> <!-- Jun 15, 2023 -->
<p>Time: {{ currentDate | date:'shortTime' }}</p> <!-- 1:30 PM -->
<p>Custom format: {{ currentDate | date:'EEEE, MMMM d, y, h:mm a' }}</p>
<!-- Monday, June 15, 2023, 1:30 PM -->
<p>GMT date: {{ currentDate | date:'medium':'GMT' }}</p>
<!-- Jun 15, 2023, 5:30:42 PM -->
</div>
The date pipe accepts many format options documented in the Angular DatePipe documentation.
Object Pipes
<div>
<!-- Debug objects with json pipe -->
<pre>{{ user | json }}</pre>
<!-- Iterate through objects with keyvalue pipe -->
<ul>
<li *ngFor="let item of userProfile | keyvalue">
{{ item.key }}: {{ item.value }}
</li>
</ul>
</div>
The json pipe is particularly useful during development for debugging purposes, as it formats JavaScript
objects in a readable way.
Array Pipe: slice
<!-- Original array: ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'] -->
<!-- Display first 3 items -->
<div *ngFor="let fruit of fruits | slice:0:3">
{{ fruit }}
</div>
<!-- Display items starting from index 2 -->
<div *ngFor="let fruit of fruits | slice:2">
{{ fruit }}
</div>
Async Pipe
The async pipe is special because it subscribes to an Observable or Promise and automatically returns the latest
value it emits. When the component is destroyed, the async pipe automatically unsubscribes, preventing memory leaks.
<!-- In component.ts -->
import { Component } from '@angular/core';
import { Observable, interval } from 'rxjs';
import { map, take } from 'rxjs/operators';
@Component({
selector: 'app-async-example',
template: `
<div>
<h3>Current count: {{ counter$ | async }}</h3>
<div *ngIf="user$ | async as user; else loading">
<h4>Welcome, {{ user.name }}!</h4>
<p>Email: {{ user.email }}</p>
</div>
<ng-template #loading>
<p>Loading user data...</p>
</ng-template>
</div>
`
})
export class AsyncExampleComponent {
// A simple Observable that emits sequential numbers
counter$ = interval(1000).pipe(
map(i => i + 1),
take(10)
);
// Simulated user data fetch
user$ = new Observable(observer => {
setTimeout(() => {
observer.next({ name: 'John Doe', email: 'john@example.com' });
}, 2000);
});
}
This example demonstrates:
- Using
asyncpipe to display the current value of an Observable - Using the "as" syntax to store the resolved value in a template variable
- Providing a loading template while waiting for data to load
Creating Custom Pipes
While Angular's built-in pipes cover many use cases, you'll often need to create custom pipes for application-specific transformations. Let's create a few examples to demonstrate the process.
Basic Custom Pipe
To create a custom pipe:
- Create a new TypeScript class
- Implement the
PipeTransforminterface - Apply the
@Pipedecorator - Register the pipe in an Angular module
Example: File Size Pipe
Let's create a pipe that formats file sizes in bytes to a human-readable format (KB, MB, GB, etc.).
// file-size.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'fileSize'
})
export class FileSizePipe implements PipeTransform {
transform(sizeInBytes: number, decimals: number = 2): string {
if (sizeInBytes === 0) {
return '0 Bytes';
}
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
const i = Math.floor(Math.log(sizeInBytes) / Math.log(k));
return parseFloat((sizeInBytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
}
}
// Register in app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FileSizePipe } from './file-size.pipe';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent,
FileSizePipe // Register the pipe here
],
imports: [
BrowserModule
],
bootstrap: [AppComponent]
})
export class AppModule { }
// Using the pipe in a template
<div>
<h3>File Downloads</h3>
<ul>
<li *ngFor="let file of files">
{{ file.name }} - {{ file.size | fileSize }}
</li>
</ul>
</div>
// Component class
export class FilesComponent {
files = [
{ name: 'Project Report.pdf', size: 3450000 },
{ name: 'Vacation Photos.zip', size: 147800000 },
{ name: 'Config.json', size: 1230 },
{ name: 'System Backup.iso', size: 4700000000 }
];
}
Parameterized Custom Pipe
Let's create a more complex pipe that filters an array based on a search term.
// filter.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'filter'
})
export class FilterPipe implements PipeTransform {
transform(items: any[], searchText: string, fieldName?: string): any[] {
if (!items) return [];
if (!searchText) return items;
searchText = searchText.toLowerCase();
return items.filter(item => {
if (fieldName) {
// If a field name is provided, only search in that field
const fieldValue = item[fieldName].toString().toLowerCase();
return fieldValue.includes(searchText);
} else {
// Otherwise, search all string fields
return Object.keys(item).some(key => {
const value = item[key];
return typeof value === 'string' && value.toLowerCase().includes(searchText);
});
}
});
}
}
// Using the filter pipe in a template
<div class="product-search">
<input type="text" placeholder="Search products..." [(ngModel)]="searchTerm">
<div class="filter-options">
<label>
<input type="radio" name="searchField" [(ngModel)]="searchField" value="">
All Fields
</label>
<label>
<input type="radio" name="searchField" [(ngModel)]="searchField" value="name">
Name Only
</label>
<label>
<input type="radio" name="searchField" [(ngModel)]="searchField" value="category">
Category Only
</label>
</div>
<div class="product-list">
<div *ngFor="let product of products | filter:searchTerm:searchField" class="product-card">
<h3>{{ product.name }}</h3>
<p>Category: {{ product.category }}</p>
<p>{{ product.description }}</p>
<p>{{ product.price | currency }}</p>
</div>
</div>
</div>
// Component class
export class ProductSearchComponent {
searchTerm = '';
searchField = '';
products = [
{
name: 'Smartphone X',
category: 'Electronics',
description: 'Latest model with advanced features',
price: 999
},
{
name: 'Running Shoes',
category: 'Sportswear',
description: 'Comfortable shoes for long-distance running',
price: 129
},
// More products...
];
}
Pure vs Impure Pipes
Angular pipes come in two flavors: pure and impure. This distinction affects when Angular re-evaluates the pipe.
Default] A --> C[Impure Pipes] B --> D[Re-evaluated only when
input value or parameters change] C --> E[Re-evaluated on every
change detection cycle] D --> F[More performant] E --> G[Can detect changes
within objects/arrays] style B fill:#d4edda style C fill:#f8d7da style F fill:#d4edda style G fill:#f8d7da
Pure Pipes (Default)
- Re-evaluated only when the input value or parameters change (by reference)
- More performant as they run less frequently
- Do not detect changes inside objects or arrays (won't update if you add an item to an array)
- Suitable for most transformations
Impure Pipes
- Re-evaluated during every change detection cycle
- Can detect changes inside objects or arrays
- Less performant due to frequent execution
- Should be used sparingly and only when necessary
Making a Pipe Impure
@Pipe({
name: 'filter',
pure: false // Mark the pipe as impure
})
export class FilterPipe implements PipeTransform {
// Implementation...
}
Performance Warning
Be cautious with impure pipes, especially when used with large data sets or complex transformations. They can significantly impact your application's performance.
For filtering large arrays, consider handling the filtering in your component class instead of using an impure pipe.
Real-World Applications
E-commerce Product Catalog
In an e-commerce application, pipes can be used to:
- Format product prices with appropriate currency symbols
- Filter products by category, price range, or search terms
- Format dates for product releases or special offers
- Convert product measurements (inches to cm, oz to grams, etc.)
Financial Dashboard
For financial applications, pipes can:
- Format large numbers with appropriate decimal places and thousand separators
- Convert between currencies with real-time exchange rates
- Format percentages for investment returns or interest rates
- Format dates for financial reports or transaction history
Social Media Platform
In social media applications, pipes can:
- Format dates as relative time (e.g., "2 minutes ago", "yesterday")
- Truncate long text posts with "read more" options
- Format user counts (e.g., 1.2K followers)
- Filter content based on user preferences or search terms
Example: Relative Time Pipe
// relative-time.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'relativeTime'
})
export class RelativeTimePipe implements PipeTransform {
transform(value: Date | string): string {
if (!value) return '';
const now = new Date();
const date = new Date(value);
const seconds = Math.floor((now.getTime() - date.getTime()) / 1000);
// Less than a minute
if (seconds < 60) {
return 'just now';
}
// Less than an hour
const minutes = Math.floor(seconds / 60);
if (minutes < 60) {
return minutes === 1 ? '1 minute ago' : `${minutes} minutes ago`;
}
// Less than a day
const hours = Math.floor(minutes / 60);
if (hours < 24) {
return hours === 1 ? '1 hour ago' : `${hours} hours ago`;
}
// Less than a month
const days = Math.floor(hours / 24);
if (days < 30) {
return days === 1 ? 'yesterday' : `${days} days ago`;
}
// Less than a year
const months = Math.floor(days / 30);
if (months < 12) {
return months === 1 ? '1 month ago' : `${months} months ago`;
}
// More than a year
const years = Math.floor(months / 12);
return years === 1 ? '1 year ago' : `${years} years ago`;
}
}
// Using the relative time pipe
<div class="social-feed">
<div *ngFor="let post of posts" class="post">
<div class="post-header">
<img [src]="post.author.avatarUrl" class="avatar">
<div>
<h4>{{ post.author.name }}</h4>
<small>{{ post.createdAt | relativeTime }}</small>
</div>
</div>
<p>{{ post.content }}</p>
<div class="post-actions">
<button>Like ({{ post.likes | number }})</button>
<button>Comment ({{ post.comments.length }})</button>
<button>Share</button>
</div>
</div>
</div>
Best Practices
Performance Considerations
- Prefer pure pipes: They're more performant and sufficient for most use cases
- Be cautious with impure pipes: Only use them when absolutely necessary
- Consider alternatives for complex operations: For expensive computations or filtering large arrays, handle the transformation in your component class
- Memoize complex pipe transformations: Cache previously computed results to avoid redundant work
Design Considerations
- Keep pipes focused: Each pipe should handle one specific transformation
- Make pipes reusable: Design pipes to be generic and reusable across your application
- Document parameters: Add clear JSDoc comments to describe input parameters
- Handle edge cases: Always account for null/undefined values and other edge cases
When Not to Use Pipes
While pipes are powerful, they aren't always the best solution:
- For complex transformations that affect application state
- When you need to filter very large arrays (performance issues)
- For transformations that involve side effects (API calls, DOM manipulation)
- When the transformation requires complex application context
Practice Activities
Basic Exercise: Create a Text Truncation Pipe
Create a pipe that truncates text after a specified number of characters and adds an ellipsis.
- Create a new pipe named
TruncatePipe - Accept parameters for maximum length and custom suffix (default: "...")
- Handle edge cases (null values, short text that doesn't need truncation)
- Test the pipe with various text lengths and parameters
Intermediate Exercise: Create a Highlight Pipe
Create a pipe that highlights search terms within text by wrapping them in HTML.
- Create a new pipe named
HighlightPipe - Accept a search term and an optional CSS class for styling
- Find all instances of the search term (case-insensitive) and wrap them in
<span>tags - Make sure the pipe is safe to use with Angular's sanitization (consider using
DomSanitizer) - Test the pipe with various text and search terms
Advanced Exercise: Build a Custom Sorting Pipe
Create a pipe that sorts arrays of objects by any specified property and direction.
- Create a new pipe named
SortByPipe - Accept parameters for the property name and sort direction (asc/desc)
- Handle different data types (strings, numbers, dates)
- Make it work with nested properties (e.g., "user.name")
- Test the pipe with various data structures and sort parameters
Summary
Angular pipes are a powerful and elegant way to transform data directly in your templates. They:
- Keep your templates clean and readable
- Promote code reuse across your application
- Separate transformation logic from component business logic
- Come with many built-in options for common transformations
- Can be extended with custom pipes for application-specific needs
By understanding both built-in and custom pipes, along with the performance implications of pure vs. impure pipes, you can effectively transform data for display in your Angular applications.
Key Takeaways
- Use the pipe symbol
|to apply transformations in templates - Angular provides many useful built-in pipes (date, currency, number, etc.)
- Custom pipes can be created by implementing the
PipeTransforminterface - Pure pipes (default) only re-evaluate when inputs change by reference
- Impure pipes re-evaluate on every change detection cycle, but can be performance-intensive
- Consider alternatives to pipes for complex operations on large data sets