Angular Pipes¶
Pipes in Angular are a powerful feature for transforming data in templates. They take data as input and transform it into a desired output format for display. Angular provides many built-in pipes for common transformations, and you can also create custom pipes for specific needs.
Understanding Pipes¶
Pipes are simple functions that accept an input value and return a transformed value. They are used in templates with the pipe operator (|):
{{ expression | pipeName }}
{{ expression | pipeName:parameter }}
{{ expression | pipeName:parameter1:parameter2 }}
Basic Pipe Usage¶
@Component({
selector: 'app-pipe-demo',
template: `
<div class="pipe-demo">
<h2>Basic Pipe Examples</h2>
<!-- Original value -->
<p>Original: {{ message }}</p>
<!-- Transformed values -->
<p>Uppercase: {{ message | uppercase }}</p>
<p>Lowercase: {{ message | lowercase }}</p>
<p>Title Case: {{ message | titlecase }}</p>
<!-- Number formatting -->
<p>Number: {{ price | currency:'USD':'symbol':'1.2-2' }}</p>
<p>Percent: {{ ratio | percent:'1.1-2' }}</p>
<!-- Date formatting -->
<p>Date: {{ today | date:'medium' }}</p>
<p>Custom Date: {{ today | date:'dd/MM/yyyy HH:mm' }}</p>
</div>
`
})
export class PipeDemoComponent {
message = 'hello angular pipes';
price = 1234.56;
ratio = 0.1234;
today = new Date();
}
Built-in Pipes¶
Angular provides a comprehensive set of built-in pipes for common transformations:
String Pipes¶
@Component({
selector: 'app-string-pipes',
template: `
<div class="string-pipes">
<h3>String Transformation Pipes</h3>
<div class="example">
<strong>Original:</strong> {{ text }}
</div>
<div class="example">
<strong>UpperCase:</strong> {{ text | uppercase }}
</div>
<div class="example">
<strong>LowerCase:</strong> {{ text | lowercase }}
</div>
<div class="example">
<strong>TitleCase:</strong> {{ text | titlecase }}
</div>
<div class="example">
<strong>Slice (0,10):</strong> {{ text | slice:0:10 }}
</div>
<div class="example">
<strong>Slice (5):</strong> {{ text | slice:5 }}
</div>
<!-- JSON Pipe for debugging -->
<div class="example">
<strong>JSON:</strong> {{ user | json }}
</div>
</div>
`
})
export class StringPipesComponent {
text = 'Angular Pipes are POWERFUL tools for data transformation';
user = {
name: 'John Doe',
age: 30,
email: 'john@example.com',
preferences: {
theme: 'dark',
language: 'en'
}
};
}
Number Pipes¶
@Component({
selector: 'app-number-pipes',
template: `
<div class="number-pipes">
<h3>Number Formatting Pipes</h3>
<!-- Decimal Pipe -->
<div class="example-group">
<h4>Decimal Pipe</h4>
<p>Original: {{ number }}</p>
<p>Default: {{ number | number }}</p>
<p>Min 1, Max 2 decimals: {{ number | number:'1.1-2' }}</p>
<p>Min 3, Max 3 decimals: {{ number | number:'1.3-3' }}</p>
<p>No decimals: {{ number | number:'1.0-0' }}</p>
</div>
<!-- Currency Pipe -->
<div class="example-group">
<h4>Currency Pipe</h4>
<p>Default USD: {{ price | currency }}</p>
<p>EUR Symbol: {{ price | currency:'EUR':'symbol' }}</p>
<p>EUR Code: {{ price | currency:'EUR':'code' }}</p>
<p>Custom Symbol: {{ price | currency:'EUR':'symbol':'1.0-0' }}</p>
<p>Different Locale: {{ price | currency:'USD':'symbol':'1.2-2':'de' }}</p>
</div>
<!-- Percent Pipe -->
<div class="example-group">
<h4>Percent Pipe</h4>
<p>Default: {{ percentage | percent }}</p>
<p>1 decimal: {{ percentage | percent:'1.1-1' }}</p>
<p>3 decimals: {{ percentage | percent:'1.3-3' }}</p>
<p>No decimals: {{ percentage | percent:'1.0-0' }}</p>
</div>
</div>
`
})
export class NumberPipesComponent {
number = 1234.5678;
price = 1234.56;
percentage = 0.1234;
}
Date Pipes¶
@Component({
selector: 'app-date-pipes',
template: `
<div class="date-pipes">
<h3>Date Formatting Pipes</h3>
<div class="example-group">
<h4>Predefined Formats</h4>
<p>Original: {{ currentDate }}</p>
<p>Short: {{ currentDate | date:'short' }}</p>
<p>Medium: {{ currentDate | date:'medium' }}</p>
<p>Long: {{ currentDate | date:'long' }}</p>
<p>Full: {{ currentDate | date:'full' }}</p>
<p>Short Date: {{ currentDate | date:'shortDate' }}</p>
<p>Medium Date: {{ currentDate | date:'mediumDate' }}</p>
<p>Long Date: {{ currentDate | date:'longDate' }}</p>
<p>Full Date: {{ currentDate | date:'fullDate' }}</p>
<p>Short Time: {{ currentDate | date:'shortTime' }}</p>
<p>Medium Time: {{ currentDate | date:'mediumTime' }}</p>
</div>
<div class="example-group">
<h4>Custom Formats</h4>
<p>dd/MM/yyyy: {{ currentDate | date:'dd/MM/yyyy' }}</p>
<p>MM-dd-yyyy: {{ currentDate | date:'MM-dd-yyyy' }}</p>
<p>yyyy-MM-dd HH:mm:ss: {{ currentDate | date:'yyyy-MM-dd HH:mm:ss' }}</p>
<p>EEEE, MMMM d, y: {{ currentDate | date:'EEEE, MMMM d, y' }}</p>
<p>h:mm a: {{ currentDate | date:'h:mm a' }}</p>
<p>HH:mm:ss: {{ currentDate | date:'HH:mm:ss' }}</p>
</div>
<div class="example-group">
<h4>Different Timezones</h4>
<p>UTC: {{ currentDate | date:'medium':'UTC' }}</p>
<p>New York: {{ currentDate | date:'medium':'America/New_York' }}</p>
<p>Tokyo: {{ currentDate | date:'medium':'Asia/Tokyo' }}</p>
<p>London: {{ currentDate | date:'medium':'Europe/London' }}</p>
</div>
<div class="example-group">
<h4>Different Locales</h4>
<p>US English: {{ currentDate | date:'full':'':'en-US' }}</p>
<p>German: {{ currentDate | date:'full':'':'de-DE' }}</p>
<p>French: {{ currentDate | date:'full':'':'fr-FR' }}</p>
<p>Japanese: {{ currentDate | date:'full':'':'ja-JP' }}</p>
</div>
</div>
`
})
export class DatePipesComponent {
currentDate = new Date();
}
Collection Pipes¶
@Component({
selector: 'app-collection-pipes',
template: `
<div class="collection-pipes">
<h3>Collection Pipes</h3>
<!-- KeyValue Pipe -->
<div class="example-group">
<h4>KeyValue Pipe</h4>
<div>
<strong>Object:</strong>
<ul>
<li *ngFor="let item of user | keyvalue">
{{ item.key }}: {{ item.value }}
</li>
</ul>
</div>
<div>
<strong>Object with Custom Sort:</strong>
<ul>
<li *ngFor="let item of user | keyvalue:customSort">
{{ item.key }}: {{ item.value }}
</li>
</ul>
</div>
<div>
<strong>Map:</strong>
<ul>
<li *ngFor="let item of userMap | keyvalue">
{{ item.key }}: {{ item.value }}
</li>
</ul>
</div>
</div>
<!-- Slice Pipe with Arrays -->
<div class="example-group">
<h4>Slice Pipe with Arrays</h4>
<p>Original Array: {{ numbers }}</p>
<p>First 3: {{ numbers | slice:0:3 }}</p>
<p>Last 3: {{ numbers | slice:-3 }}</p>
<p>Middle (2-5): {{ numbers | slice:2:5 }}</p>
<p>From index 3: {{ numbers | slice:3 }}</p>
</div>
</div>
`
})
export class CollectionPipesComponent {
user = {
name: 'John Doe',
age: 30,
email: 'john@example.com',
city: 'New York'
};
userMap = new Map([
['id', 1],
['username', 'johndoe'],
['active', true],
['lastLogin', new Date()]
]);
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
customSort = (a: any, b: any) => {
return a.key.localeCompare(b.key);
};
}
Pipe Parameters¶
Pipes can accept parameters to customize their behavior:
@Component({
selector: 'app-pipe-parameters',
template: `
<div class="pipe-parameters">
<h3>Pipe Parameters</h3>
<!-- Single Parameter -->
<p>Slice with single parameter: {{ text | slice:10 }}</p>
<!-- Multiple Parameters -->
<p>Slice with multiple parameters: {{ text | slice:5:15 }}</p>
<!-- Currency with parameters -->
<p>Currency USD: {{ amount | currency:'USD':'symbol':'1.2-2' }}</p>
<p>Currency EUR: {{ amount | currency:'EUR':'code':'1.0-0' }}</p>
<!-- Date with parameters -->
<p>Date format: {{ date | date:'dd/MM/yyyy':'UTC':locale }}</p>
<!-- Number with parameters -->
<p>Number format: {{ number | number:'1.2-4':locale }}</p>
<!-- Dynamic parameters -->
<div class="controls">
<label>
Currency:
<select [(ngModel)]="selectedCurrency">
<option value="USD">USD</option>
<option value="EUR">EUR</option>
<option value="GBP">GBP</option>
<option value="JPY">JPY</option>
</select>
</label>
<label>
Date Format:
<select [(ngModel)]="selectedDateFormat">
<option value="short">Short</option>
<option value="medium">Medium</option>
<option value="long">Long</option>
<option value="dd/MM/yyyy">dd/MM/yyyy</option>
<option value="MM-dd-yyyy HH:mm">MM-dd-yyyy HH:mm</option>
</select>
</label>
</div>
<div class="dynamic-results">
<p>Dynamic Currency: {{ amount | currency:selectedCurrency:'symbol':'1.2-2' }}</p>
<p>Dynamic Date: {{ date | date:selectedDateFormat }}</p>
</div>
</div>
`
})
export class PipeParametersComponent {
text = 'Angular Pipes with Parameters';
amount = 1234.567;
date = new Date();
number = 12345.6789;
locale = 'en-US';
selectedCurrency = 'USD';
selectedDateFormat = 'medium';
}
Chaining Pipes¶
You can chain multiple pipes together to apply multiple transformations:
@Component({
selector: 'app-pipe-chaining',
template: `
<div class="pipe-chaining">
<h3>Pipe Chaining</h3>
<!-- String transformations -->
<div class="example-group">
<h4>String Chaining</h4>
<p>Original: {{ message }}</p>
<p>Slice + Uppercase: {{ message | slice:0:10 | uppercase }}</p>
<p>Uppercase + Slice: {{ message | uppercase | slice:0:15 }}</p>
<p>Titlecase + Slice: {{ message | titlecase | slice:0:20 }}</p>
</div>
<!-- Number and currency chaining -->
<div class="example-group">
<h4>Number Chaining</h4>
<p>Original: {{ price }}</p>
<p>Currency + Uppercase: {{ price | currency:'USD' | uppercase }}</p>
<p>Number + Slice: {{ largeNumber | number:'1.0-0' | slice:0:5 }}</p>
</div>
<!-- Date chaining -->
<div class="example-group">
<h4>Date Chaining</h4>
<p>Original: {{ today }}</p>
<p>Date + Uppercase: {{ today | date:'full' | uppercase }}</p>
<p>Date + Slice: {{ today | date:'full' | slice:0:20 }}</p>
<p>Date + Titlecase: {{ today | date:'EEEE, MMMM d, y' | titlecase }}</p>
</div>
<!-- Complex chaining -->
<div class="example-group">
<h4>Complex Chaining</h4>
<p>User JSON + Slice: {{ user | json | slice:0:50 }}</p>
<p>Array + Slice + JSON: {{ numbers | slice:0:5 | json }}</p>
</div>
</div>
`
})
export class PipeChainingComponent {
message = 'pipe chaining demonstration in angular';
price = 1234.56;
largeNumber = 1234567890;
today = new Date();
user = {
name: 'John Doe',
email: 'john@example.com',
preferences: { theme: 'dark', notifications: true }
};
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
}
Creating Custom Pipes¶
Basic Custom Pipe¶
// capitalize.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'capitalize'
})
export class CapitalizePipe implements PipeTransform {
transform(value: string): string {
if (!value) return value;
return value.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ');
}
}
Custom Pipe with Parameters¶
// truncate.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'truncate'
})
export class TruncatePipe implements PipeTransform {
transform(
value: string,
limit: number = 50,
trail: string = '...'
): string {
if (!value) return value;
if (value.length <= limit) {
return value;
}
return value.substring(0, limit) + trail;
}
}
Advanced Custom Pipe¶
// filter.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'filter',
pure: false // Make it impure to detect array changes
})
export class FilterPipe implements PipeTransform {
transform(
items: any[],
searchTerm: string,
property?: string
): any[] {
if (!items || !searchTerm) {
return items;
}
searchTerm = searchTerm.toLowerCase();
return items.filter(item => {
if (property) {
return item[property]?.toString().toLowerCase().includes(searchTerm);
} else {
return JSON.stringify(item).toLowerCase().includes(searchTerm);
}
});
}
}
Custom Pipe for Complex Transformations¶
// highlight.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
@Pipe({
name: 'highlight'
})
export class HighlightPipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {}
transform(
text: string,
search: string,
className: string = 'highlight'
): SafeHtml {
if (!text || !search) {
return text;
}
const regex = new RegExp(`(${search})`, 'gi');
const highlighted = text.replace(
regex,
`<span class="${className}">$1</span>`
);
return this.sanitizer.bypassSecurityTrustHtml(highlighted);
}
}
Using Custom Pipes¶
@Component({
selector: 'app-custom-pipes',
template: `
<div class="custom-pipes">
<h3>Custom Pipes Demo</h3>
<!-- Capitalize Pipe -->
<div class="example-group">
<h4>Capitalize Pipe</h4>
<p>Original: {{ text }}</p>
<p>Capitalized: {{ text | capitalize }}</p>
</div>
<!-- Truncate Pipe -->
<div class="example-group">
<h4>Truncate Pipe</h4>
<p>Original: {{ longText }}</p>
<p>Truncated (default): {{ longText | truncate }}</p>
<p>Truncated (20 chars): {{ longText | truncate:20 }}</p>
<p>Truncated (custom trail): {{ longText | truncate:30:'...[more]' }}</p>
</div>
<!-- Filter Pipe -->
<div class="example-group">
<h4>Filter Pipe</h4>
<input [(ngModel)]="searchTerm" placeholder="Search users...">
<ul>
<li *ngFor="let user of users | filter:searchTerm:'name'">
{{ user.name }} - {{ user.email }}
</li>
</ul>
</div>
<!-- Highlight Pipe -->
<div class="example-group">
<h4>Highlight Pipe</h4>
<input [(ngModel)]="highlightTerm" placeholder="Text to highlight...">
<p [innerHTML]="sampleText | highlight:highlightTerm"></p>
</div>
</div>
`,
styles: [`
.highlight {
background-color: yellow;
font-weight: bold;
}
.example-group {
margin-bottom: 2rem;
padding: 1rem;
border: 1px solid #ddd;
border-radius: 4px;
}
`]
})
export class CustomPipesComponent {
text = 'hello world from angular';
longText = 'This is a very long text that needs to be truncated because it contains too much information for the available space in the user interface.';
searchTerm = '';
highlightTerm = '';
sampleText = 'Angular pipes are a great way to transform data in templates. They provide a clean and declarative way to format and modify data for display purposes.';
users = [
{ name: 'John Doe', email: 'john@example.com' },
{ name: 'Jane Smith', email: 'jane@example.com' },
{ name: 'Bob Johnson', email: 'bob@example.com' },
{ name: 'Alice Brown', email: 'alice@example.com' }
];
}
Pure vs Impure Pipes¶
Pure Pipes (Default)¶
Pure pipes are executed only when Angular detects a pure change to the input value:
// A pure pipe (default behavior)
@Pipe({
name: 'purePipe',
pure: true // This is the default
})
export class PurePipe implements PipeTransform {
transform(value: any[]): any[] {
console.log('Pure pipe executed'); // Called only when array reference changes
return value.filter(item => item.active);
}
}
Impure Pipes¶
Impure pipes are executed on every change detection cycle:
// An impure pipe
@Pipe({
name: 'impurePipe',
pure: false
})
export class ImpurePipe implements PipeTransform {
transform(value: any[]): any[] {
console.log('Impure pipe executed'); // Called on every change detection
return value.filter(item => item.active);
}
}
Comparison Example¶
@Component({
selector: 'app-pure-impure',
template: `
<div class="pure-impure-demo">
<h3>Pure vs Impure Pipes</h3>
<button (click)="addItem()">Add Item</button>
<button (click)="toggleFirstItem()">Toggle First Item</button>
<button (click)="updateCounter()">Update Counter ({{ counter }})</button>
<div class="results">
<h4>Pure Pipe Result:</h4>
<ul>
<li *ngFor="let item of items | purePipe">
{{ item.name }} - {{ item.active ? 'Active' : 'Inactive' }}
</li>
</ul>
<h4>Impure Pipe Result:</h4>
<ul>
<li *ngFor="let item of items | impurePipe">
{{ item.name }} - {{ item.active ? 'Active' : 'Inactive' }}
</li>
</ul>
</div>
</div>
`
})
export class PureImpureComponent {
counter = 0;
items = [
{ name: 'Item 1', active: true },
{ name: 'Item 2', active: false },
{ name: 'Item 3', active: true }
];
addItem(): void {
// This creates a new array reference, so pure pipe will execute
this.items = [...this.items, {
name: `Item ${this.items.length + 1}`,
active: Math.random() > 0.5
}];
}
toggleFirstItem(): void {
// This modifies the array in place, pure pipe won't execute
this.items[0].active = !this.items[0].active;
}
updateCounter(): void {
// This doesn't affect the array, but impure pipe will still execute
this.counter++;
}
}
Async Pipe¶
The async pipe is a special built-in pipe for handling asynchronous data:
// data.service.ts
import { Injectable } from '@angular/core';
import { Observable, interval, of, delay } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class DataService {
// Observable that emits current time every second
getCurrentTime(): Observable<Date> {
return interval(1000).pipe(
map(() => new Date())
);
}
// Observable that simulates API call
getUsers(): Observable<any[]> {
const users = [
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' },
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com' }
];
return of(users).pipe(delay(2000));
}
// Observable that emits numbers
getNumbers(): Observable<number[]> {
return interval(1000).pipe(
map(i => Array.from({ length: i + 1 }, (_, index) => index + 1)),
startWith([])
);
}
}
@Component({
selector: 'app-async-pipe',
template: `
<div class="async-pipe-demo">
<h3>Async Pipe Demo</h3>
<!-- Simple observable -->
<div class="example-group">
<h4>Current Time</h4>
<p>{{ currentTime$ | async | date:'medium' }}</p>
</div>
<!-- Observable with loading state -->
<div class="example-group">
<h4>Users (with loading state)</h4>
<div *ngIf="users$ | async as users; else loading">
<ul>
<li *ngFor="let user of users">
{{ user.name }} - {{ user.email }}
</li>
</ul>
</div>
<ng-template #loading>
<p>Loading users...</p>
</ng-template>
</div>
<!-- Multiple async pipes (inefficient) -->
<div class="example-group">
<h4>Multiple Async Pipes (Inefficient)</h4>
<div *ngIf="users$ | async">
<p>User count: {{ (users$ | async)?.length }}</p>
<p>First user: {{ (users$ | async)?.[0]?.name }}</p>
</div>
</div>
<!-- Single async pipe with alias (efficient) -->
<div class="example-group">
<h4>Single Async Pipe with Alias (Efficient)</h4>
<div *ngIf="users$ | async as users">
<p>User count: {{ users.length }}</p>
<p>First user: {{ users[0]?.name }}</p>
<ul>
<li *ngFor="let user of users">{{ user.name }}</li>
</ul>
</div>
</div>
<!-- Dynamic numbers -->
<div class="example-group">
<h4>Dynamic Numbers</h4>
<p>Numbers: {{ numbers$ | async | json }}</p>
<p>Sum: {{ (numbers$ | async)?.reduce((a, b) => a + b, 0) }}</p>
</div>
<!-- Promise support -->
<div class="example-group">
<h4>Promise Support</h4>
<p>Promise result: {{ promiseData | async }}</p>
</div>
</div>
`
})
export class AsyncPipeComponent implements OnInit {
currentTime$!: Observable<Date>;
users$!: Observable<any[]>;
numbers$!: Observable<number[]>;
promiseData!: Promise<string>;
constructor(private dataService: DataService) {}
ngOnInit(): void {
this.currentTime$ = this.dataService.getCurrentTime();
this.users$ = this.dataService.getUsers();
this.numbers$ = this.dataService.getNumbers();
// Promise example
this.promiseData = new Promise(resolve => {
setTimeout(() => {
resolve('Promise resolved after 3 seconds');
}, 3000);
});
}
}
Advanced Pipe Techniques¶
Memoized Pipes for Performance¶
// memoized.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'memoized'
})
export class MemoizedPipe implements PipeTransform {
private cache = new Map<string, any>();
transform(value: any, ...args: any[]): any {
const key = JSON.stringify([value, ...args]);
if (this.cache.has(key)) {
console.log('Cache hit');
return this.cache.get(key);
}
console.log('Computing result');
const result = this.expensiveOperation(value, ...args);
this.cache.set(key, result);
return result;
}
private expensiveOperation(value: any, ...args: any[]): any {
// Simulate expensive computation
return value?.toString().repeat(args[0] || 1);
}
}
Conditional Pipes¶
// conditional.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'conditional'
})
export class ConditionalPipe implements PipeTransform {
transform(
value: any,
condition: boolean,
trueValue: any,
falseValue: any = value
): any {
return condition ? trueValue : falseValue;
}
}
Safe Navigation Pipe¶
// safe.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'safe'
})
export class SafePipe implements PipeTransform {
transform(value: any, path: string): any {
if (!value || !path) return value;
return path.split('.').reduce((obj, key) => {
return obj && obj[key] !== undefined ? obj[key] : null;
}, value);
}
}
Usage of Advanced Pipes¶
@Component({
selector: 'app-advanced-pipes',
template: `
<div class="advanced-pipes">
<h3>Advanced Pipe Techniques</h3>
<!-- Memoized Pipe -->
<div class="example-group">
<h4>Memoized Pipe</h4>
<input [(ngModel)]="memoValue" placeholder="Enter text...">
<input [(ngModel)]="repeatCount" type="number" placeholder="Repeat count...">
<p>Result: {{ memoValue | memoized:repeatCount }}</p>
</div>
<!-- Conditional Pipe -->
<div class="example-group">
<h4>Conditional Pipe</h4>
<label>
<input type="checkbox" [(ngModel)]="showFullName"> Show Full Name
</label>
<p>Name: {{ user.firstName | conditional:showFullName:user.fullName:user.firstName }}</p>
</div>
<!-- Safe Navigation Pipe -->
<div class="example-group">
<h4>Safe Navigation Pipe</h4>
<p>User Name: {{ user | safe:'profile.name' }}</p>
<p>User Email: {{ user | safe:'profile.contact.email' }}</p>
<p>Invalid Path: {{ user | safe:'invalid.path.here' }}</p>
</div>
</div>
`
})
export class AdvancedPipesComponent {
memoValue = 'test';
repeatCount = 2;
showFullName = false;
user = {
firstName: 'John',
fullName: 'John Doe',
profile: {
name: 'John Doe',
contact: {
email: 'john@example.com'
}
}
};
}
Performance Considerations¶
Pipe Performance Best Practices¶
// ✅ Good: Pure pipe for simple transformations
@Pipe({
name: 'multiply',
pure: true
})
export class MultiplyPipe implements PipeTransform {
transform(value: number, factor: number): number {
return value * factor;
}
}
// ❌ Bad: Impure pipe for simple transformations
@Pipe({
name: 'multiplyImpure',
pure: false
})
export class MultiplyImpurePipe implements PipeTransform {
transform(value: number, factor: number): number {
console.log('Expensive calculation executed');
return value * factor;
}
}
// ✅ Good: Memoization for expensive operations
@Pipe({
name: 'expensiveOperation'
})
export class ExpensiveOperationPipe implements PipeTransform {
private cache = new Map();
transform(value: any): any {
if (this.cache.has(value)) {
return this.cache.get(value);
}
const result = this.performExpensiveOperation(value);
this.cache.set(value, result);
return result;
}
private performExpensiveOperation(value: any): any {
// Expensive computation here
return value;
}
}
Template Optimization¶
<!-- ❌ Bad: Multiple async pipe subscriptions -->
<div *ngIf="data$ | async">
<p>Count: {{ (data$ | async)?.length }}</p>
<p>First: {{ (data$ | async)?.[0] }}</p>
</div>
<!-- ✅ Good: Single async pipe with alias -->
<div *ngIf="data$ | async as data">
<p>Count: {{ data.length }}</p>
<p>First: {{ data[0] }}</p>
</div>
<!-- ✅ Good: Store in component property -->
<div *ngIf="data">
<p>Count: {{ data.length }}</p>
<p>First: {{ data[0] }}</p>
</div>
Best Practices¶
1. Choose the Right Pipe Type¶
// Use pure pipes for deterministic transformations
@Pipe({ name: 'capitalize', pure: true })
export class CapitalizePipe implements PipeTransform {
transform(value: string): string {
return value ? value.charAt(0).toUpperCase() + value.slice(1) : value;
}
}
// Use impure pipes only when necessary
@Pipe({ name: 'dynamicFilter', pure: false })
export class DynamicFilterPipe implements PipeTransform {
transform(items: any[], filterFn: (item: any) => boolean): any[] {
return items?.filter(filterFn) || [];
}
}
2. Error Handling¶
@Pipe({ name: 'safeTransform' })
export class SafeTransformPipe implements PipeTransform {
transform(value: any, operation: string): any {
try {
switch (operation) {
case 'upper':
return value?.toString().toUpperCase();
case 'lower':
return value?.toString().toLowerCase();
case 'reverse':
return value?.toString().split('').reverse().join('');
default:
return value;
}
} catch (error) {
console.error('Pipe error:', error);
return value; // Return original value on error
}
}
}
3. Type Safety¶
interface User {
id: number;
name: string;
email: string;
}
@Pipe({ name: 'userFilter' })
export class UserFilterPipe implements PipeTransform {
transform(users: User[], searchTerm: string): User[] {
if (!users || !searchTerm) {
return users;
}
return users.filter(user =>
user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.email.toLowerCase().includes(searchTerm.toLowerCase())
);
}
}
4. Testing Pipes¶
// user-filter.pipe.spec.ts
import { UserFilterPipe } from './user-filter.pipe';
describe('UserFilterPipe', () => {
let pipe: UserFilterPipe;
const mockUsers = [
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' }
];
beforeEach(() => {
pipe = new UserFilterPipe();
});
it('should create an instance', () => {
expect(pipe).toBeTruthy();
});
it('should filter users by name', () => {
const result = pipe.transform(mockUsers, 'john');
expect(result.length).toBe(1);
expect(result[0].name).toBe('John Doe');
});
it('should filter users by email', () => {
const result = pipe.transform(mockUsers, 'jane@example.com');
expect(result.length).toBe(1);
expect(result[0].email).toBe('jane@example.com');
});
it('should return all users when search term is empty', () => {
const result = pipe.transform(mockUsers, '');
expect(result.length).toBe(2);
});
it('should handle null/undefined inputs', () => {
expect(pipe.transform(null as any, 'test')).toBeNull();
expect(pipe.transform(mockUsers, null as any)).toBe(mockUsers);
});
});
Summary¶
Angular pipes are powerful tools for data transformation in templates:
Key Concepts:
- Built-in pipes: String, number, date, collection transformations
- Custom pipes: Create reusable transformation logic
- Pipe parameters: Customize pipe behavior
- Pipe chaining: Combine multiple transformations
- Pure vs Impure: Performance considerations
- Async pipe: Handle asynchronous data
Best Practices:
- Use pure pipes for deterministic transformations
- Implement error handling in custom pipes
- Use TypeScript for type safety
- Cache expensive operations with memoization
- Minimize use of impure pipes
- Use async pipe with aliases to avoid multiple subscriptions
- Write comprehensive tests for custom pipes
Performance Tips:
- Prefer pure pipes over impure pipes
- Use single async pipe with template aliases
- Implement caching for expensive operations
- Avoid complex logic in pipe transforms
Next, we'll explore Angular services and dependency injection patterns for managing application state and business logic.