Creating a Custom Pipe
Custom pipes encapsulate reusable transformation logic for templates. They implement the PipeTransform interface.
Basic Custom Pipe
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'fileSize',
standalone: true,
})
export class FileSizePipe implements PipeTransform {
transform(bytes: number): string {
if (bytes === 0) return '0 Bytes';
const k = 1024;
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(2)) + ' ' + sizes[i];
}
}
<p>{{ file.size | fileSize }}</p>
<!-- Output: 1.5 MB -->
Pipe with Parameters
@Pipe({
name: 'truncate',
standalone: true,
})
export class TruncatePipe implements PipeTransform {
transform(value: string, limit: number = 50, suffix: string = '...'): string {
if (value.length <= limit) return value;
return value.substring(0, limit) + suffix;
}
}
<p>{{ longText | truncate }}</p>
<p>{{ longText | truncate: 100 }}</p>
<p>{{ longText | truncate: 100 : ' →' }}</p>
Registering Pipes
Standalone (Angular 14+):
@Component({
selector: 'app-example',
imports: [FileSizePipe, TruncatePipe],
template: `<p>{{ size | fileSize }}</p>`,
})
export class ExampleComponent {}
NgModule:
@NgModule({
declarations: [FileSizePipe, TruncatePipe],
exports: [FileSizePipe, TruncatePipe],
})
export class SharedModule {}
Advanced Pipe Patterns
Pipes can handle complex transformations, work with objects, and compose with other pipes.
Pipe with Object Input
@Pipe({
name: 'groupBy',
standalone: true,
})
export class GroupByPipe implements PipeTransform {
transform<T>(items: T[], key: keyof T): Map<string, T[]> {
const groups = new Map<string, T[]>();
for (const item of items) {
const groupKey = String(item[key]);
if (!groups.has(groupKey)) {
groups.set(groupKey, []);
}
groups.get(groupKey)!.push(item);
}
return groups;
}
}
@for (group of items | groupBy: 'category' | keyvalue; track group.key) {
<h3>{{ group.key }}</h3>
<p *ngFor="let item of group.value">{{ item.name }}</p>
}
Pipe with Dependency Injection
@Pipe({
name: 'currencyConvert',
standalone: true,
})
export class CurrencyConvertPipe implements PipeTransform {
private rates = inject(ExchangeRateService);
transform(amount: number, from: string, to: string): number {
const rate = this.rates.getRate(from, to);
return amount * rate;
}
}
Composing Pipes
<p>{{ price | currencyConvert: 'USD': 'EUR' | currency: 'EUR' }}</p>
Best Practices
- Keep pipes pure — same input always produces same output
- Handle null/undefined — always guard against missing values
- Keep transformations lightweight — pipes run on every change detection
- Use for display logic only — do not mutate state in pipes
Quiz
1. What interface must a custom pipe implement?
2. How do you pass parameters to a pipe?
3. How do you register a standalone pipe in a component?
Flashcards
Question
How do you create a custom pipe?
Click to reveal answer
Answer
@Pipe({ name: 'pipeName', standalone: true }) on a class implementing PipeTransform with a transform() method.
Question
How do you pass parameters to a pipe?
Click to reveal answer
Answer
{{ value | pipe: param1: param2 }} — each parameter is a separate argument to transform().
Question
How do you register a standalone pipe?
Click to reveal answer
Answer
Add it to the component's imports array.
Revision Notes
Key Takeaways
- 1. Custom pipes implement PipeTransform with a transform() method
- 2. Parameters are passed with colon syntax: pipe:param1:param2
- 3. Standalone pipes are added to component imports
- 4. Keep pipes pure — same input always produces same output
Interview Tips
- • Explain the PipeTransform interface
- • Be ready to describe pipe parameter syntax
- • Mention that pipes should be pure for performance
Cheat Sheet
Custom Pipes Cheat Sheet
Create pipe:
@Pipe({ name: 'fileSize', standalone: true })
export class FileSizePipe implements PipeTransform {
transform(bytes: number): string {
// transformation logic
}
}
With parameters:
transform(value: string, limit: number = 50): string {
return value.substring(0, limit);
}
Usage:
<p>{{ size | fileSize }}</p>
<p>{{ text | truncate: 100 }}</p>