Angular Pipes
- Pipes are
simple functionsthattransform input values to output values for display in templates. - They are
used in template expressionstoformat data.
Pure vs Impure Pipes - Key Differences
Section titled “Pure vs Impure Pipes - Key Differences”| Aspect | Pure Pipe | Impure Pipe |
|---|---|---|
| Execution | Only on pure change | Every change detection |
| Performance | Optimized | Potentially slow |
| Use Case | Primitive values, immutable objects | Arrays/objects with internal mutations |
| State | Stateless | Can be stateful |
Built-in Pipes
// Template usage{{ value | pipeName:parameter }}
// Examples with built-in pipes{{ user.name | uppercase }} // JOHN{{ user.salary | currency:'USD' }} // $50,000.00{{ user.birthday | date:'mediumDate' }} // Jan 1, 2023{{ user.description | slice:0:100 }} // First 100 characters{{ items | array | json }} // JSON representationPure Pipes (Default)
@Pipe({ name: "pureExample", pure: true, // Default, can be omitted})export class PureExamplePipe implements PipeTransform { transform(value: string): string { console.log("Pure pipe executed"); return value.toUpperCase(); }}
// Usage - only runs when 'name' reference changes{ { name | pureExample; }}Impure Pipes
@Pipe({ name: "impureExample", pure: false, // Explicitly set to false})export class ImpureExamplePipe implements PipeTransform { transform(value: any[]): any[] { console.log("Impure pipe executed - runs frequently!"); return value.filter((item) => item.active); }}
// Usage - runs on every change detection{ { items | impureExample; }}Creating Custom Pipes
1. Basic Custom Pipe
Section titled “1. Basic Custom Pipe”// reverse.pipe.tsimport { Pipe, PipeTransform } from "@angular/core";
@Pipe({ name: "reverse",})export class ReversePipe implements PipeTransform { transform(value: string): string { if (!value) return value; return value.split("").reverse().join(""); }}
// Usage{ { "hello" | reverse; }} // Output: "olleh"2. Pipe with Parameters
Section titled “2. Pipe with Parameters”// truncate.pipe.ts@Pipe({ name: 'truncate'})export class TruncatePipe implements PipeTransform { transform(value: string, limit: number = 50, suffix: string = '...'): string { if (!value) return value; if (value.length <= limit) return value;
return value.substring(0, limit) + suffix; }}
// Usage{{ longText | truncate:100:'...' }}{{ longText | truncate:50 }} // Uses default suffix{{ longText | truncate }} // Uses both defaults3. Pure Pipe Example (Recommended)
Section titled “3. Pure Pipe Example (Recommended)”// filter.pipe.ts@Pipe({ name: 'filter', pure: true // Default behavior})export class FilterPipe implements PipeTransform { transform(items: any[], searchTerm: string, property: string = 'name'): any[] { if (!items || !searchTerm) return items;
return items.filter(item => item[property].toLowerCase().includes(searchTerm.toLowerCase()) ); }}
// Usage<div *ngFor="let user of users | filter:searchText:'email'"> {{ user.name }}</div>4. Impure Pipe Example (Use Carefully)
Section titled “4. Impure Pipe Example (Use Carefully)”// sort.pipe.ts@Pipe({ name: 'sort', pure: false // Needed for array sorting})export class SortPipe implements PipeTransform { transform(items: any[], field: string = 'name', order: 'asc' | 'desc' = 'asc'): any[] { if (!items || !Array.isArray(items)) return items;
return [...items].sort((a, b) => { const aVal = a[field]; const bVal = b[field];
if (order === 'asc') { return aVal < bVal ? -1 : aVal > bVal ? 1 : 0; } else { return aVal > bVal ? -1 : aVal < bVal ? 1 : 0; } }); }}
// Usage - updates when array items change<div *ngFor="let user of users | sort:'name':'asc'"> {{ user.name }}</div>5. Async Data Pipe
Section titled “5. Async Data Pipe”// safe.pipe.ts@Pipe({ name: 'safe'})export class SafePipe implements PipeTransform { constructor(private sanitizer: DomSanitizer) {}
transform(value: string, type: 'html' | 'url' | 'resourceUrl' = 'html'): any { if (!value) return value;
switch (type) { case 'html': return this.sanitizer.bypassSecurityTrustHtml(value); case 'url': return this.sanitizer.bypassSecurityTrustUrl(value); case 'resourceUrl': return this.sanitizer.bypassSecurityTrustResourceUrl(value); default: return value; } }}
// Usage for dangerous HTML<div [innerHTML]="userBio | safe:'html'"></div>6. Currency Format Pipe
Section titled “6. Currency Format Pipe”// custom-currency.pipe.ts@Pipe({ name: 'customCurrency'})export class CustomCurrencyPipe implements PipeTransform { transform( value: number, currencyCode: string = 'USD', display: 'symbol' | 'code' | 'name' = 'symbol', digits: string = '1.2-2' ): string { if (value == null) return '';
const formatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: currencyCode, minimumFractionDigits: 2, maximumFractionDigits: 2 });
return formatter.format(value); }}
// Usage{{ price | customCurrency:'EUR':'symbol' }}{{ price | customCurrency:'USD' }}7. File Size Pipe
Section titled “7. File Size Pipe”// file-size.pipe.ts@Pipe({ name: 'fileSize'})export class FileSizePipe implements PipeTransform { transform(bytes: number, decimals: number = 2): string { if (bytes === 0) return '0 Bytes';
const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; }}
// Usage{{ file.size | fileSize }} // 1.45 MB{{ file.size | fileSize:1 }} // 1.4 MBRegistering Pipes
#### Module Registration// app.module.tsimport { NgModule } from "@angular/core";import { ReversePipe, TruncatePipe, FilterPipe } from "./pipes";
@NgModule({ declarations: [ReversePipe, TruncatePipe, FilterPipe], exports: [ // Export if used in other modules ReversePipe, TruncatePipe, FilterPipe, ],})export class AppModule {}Standalone Component Registration (Angular 14+)
Section titled “Standalone Component Registration (Angular 14+)”// For standalone components@Component({ standalone: true, imports: [ReversePipe, CommonModule], template: `{{ text | reverse }}`,})export class MyComponent {}Best Practices
1. Prefer Pure Pipes
Section titled “1. Prefer Pure Pipes”// ✅ Good - Pure pipe@Pipe({ name: "calculate" })export class CalculatePipe implements PipeTransform { transform(value: number, multiplier: number): number { return value * multiplier; }}
// ❌ Avoid - Impure unless necessary@Pipe({ name: "filterArray", pure: false })export class FilterArrayPipe implements PipeTransform { transform(items: any[]) { return items.filter((item) => item.active); // Runs frequently! }}2. Use Methods for Complex Logic
Section titled “2. Use Methods for Complex Logic”// ❌ Don't put heavy logic in pipes (runs often){{ data | heavyCalculation }}
// ✅ Better - Compute in component// component.tsget calculatedData() { return this.heavyCalculation(this.data);}
// template.html{{ calculatedData }}3. Chain Pipes Properly
Section titled “3. Chain Pipes Properly”// ✅ Good - Chaining pipes{{ user.name | uppercase | truncate:20 }}
// Execution order: uppercase → truncate4. Handle Null Values
Section titled “4. Handle Null Values”@Pipe({ name: "safeDisplay" })export class SafeDisplayPipe implements PipeTransform { transform(value: any): string { // Always handle null/undefined if (value == null) return "-"; if (value === "") return "Empty";
return value.toString(); }}Performance Considerations
Pure Pipe Optimization
Section titled “Pure Pipe Optimization”@Pipe({ name: "expensive", pure: true })export class ExpensivePipe implements PipeTransform { // Cache results for same inputs private cache = new Map<string, any>();
transform(value: string, param: number): any { const key = `${value}-${param}`;
if (this.cache.has(key)) { return this.cache.get(key); }
const result = this.expensiveOperation(value, param); this.cache.set(key, result);
return result; }
private expensiveOperation(value: string, param: number) { // Heavy computation here return computedResult; }}Testing Pipes
// reverse.pipe.spec.tsimport { ReversePipe } from "./reverse.pipe";
describe("ReversePipe", () => { let pipe: ReversePipe;
beforeEach(() => { pipe = new ReversePipe(); });
it("should reverse a string", () => { expect(pipe.transform("hello")).toBe("olleh"); });
it("should handle empty string", () => { expect(pipe.transform("")).toBe(""); });
it("should handle null", () => { expect(pipe.transform(null)).toBeNull(); });});Summary
Section titled “Summary”When to use Pipes:
- Simple data transformations for display
- Formatting dates, currencies, text
- Filtering/sorting small datasets
When NOT to use Pipes:
- Heavy computations (use component methods)
- Complex business logic (use services)
- Large dataset operations (do it in component)
Pure vs Impure:
- Use Pure (default) for most cases - better performance
- Use Impure only when you need to detect changes within arrays/objects
- Be careful with impure pipes - they run frequently!
Pipes are powerful for template data transformation but should be used judiciously to maintain performance.