Skip to content
intermediate Phase 6 · Pipes and Component Styling

Writing Custom Pipes

Create a reusable transform function registered under a pipe name.

40m
0 problems
Topic Progress 0%

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

  1. Keep pipes pure — same input always produces same output
  2. Handle null/undefined — always guard against missing values
  3. Keep transformations lightweight — pipes run on every change detection
  4. Use for display logic only — do not mutate state in pipes

Quiz

1. What interface must a custom pipe implement?

Question 1 options

2. How do you pass parameters to a pipe?

Question 2 options

3. How do you register a standalone pipe in a component?

Question 3 options

Flashcards

Question

How do you create a custom pipe?

Answer

@Pipe({ name: 'pipeName', standalone: true }) on a class implementing PipeTransform with a transform() method.

Question

How do you pass parameters to a pipe?

Answer

{{ value | pipe: param1: param2 }} — each parameter is a separate argument to transform().

Question

How do you register a standalone pipe?

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>

Continue Learning

Before You Start

Next Topics

Related Reading