Skip to main content
Pipes are simple functions used in templates to transform data for display. Angular provides built-in pipes for common transformations and allows you to create custom pipes for specific needs.

What are Pipes?

Pipes take data as input and transform it to a desired output format. They are used in template expressions:
{{ value | pipeName }}
{{ value | pipeName: arg1:arg2 }}
{{ value | pipe1 | pipe2 | pipe3 }}
Pipes are pure by default, meaning they only execute when their input reference changes. This ensures optimal performance.

Built-in Pipes

Angular provides several built-in pipes:

DatePipe

Format dates:
import { Component } from '@angular/core';
import { DatePipe } from '@angular/common';

@Component({
  selector: 'app-date-demo',
  standalone: true,
  imports: [DatePipe],
  template: `
    <p>{{ today | date }}</p>
    <p>{{ today | date:'short' }}</p>
    <p>{{ today | date:'medium' }}</p>
    <p>{{ today | date:'long' }}</p>
    <p>{{ today | date:'full' }}</p>
    <p>{{ today | date:'shortDate' }}</p>
    <p>{{ today | date:'mediumTime' }}</p>
    <p>{{ today | date:'yyyy-MM-dd' }}</p>
    <p>{{ today | date:'h:mm a' }}</p>
    <p>{{ today | date:'EEEE, MMMM d, y' }}</p>
  `
})
export class DateDemoComponent {
  today = new Date();
}

CurrencyPipe

Format currency values:
import { Component } from '@angular/core';
import { CurrencyPipe } from '@angular/common';

@Component({
  selector: 'app-currency-demo',
  standalone: true,
  imports: [CurrencyPipe],
  template: `
    <p>{{ price | currency }}</p>
    <p>{{ price | currency:'EUR' }}</p>
    <p>{{ price | currency:'GBP':'symbol':'1.2-2' }}</p>
    <p>{{ price | currency:'USD':'code' }}</p>
    <p>{{ largeAmount | currency:'USD':'symbol-narrow':'1.0-0' }}</p>
  `
})
export class CurrencyDemoComponent {
  price = 123.456;
  largeAmount = 1234567.89;
}

DecimalPipe

Format numbers:
import { Component } from '@angular/core';
import { DecimalPipe } from '@angular/common';

@Component({
  selector: 'app-decimal-demo',
  standalone: true,
  imports: [DecimalPipe],
  template: `
    <p>{{ pi | number }}</p>
    <p>{{ pi | number:'1.2-4' }}</p>
    <p>{{ largeNumber | number:'1.0-0' }}</p>
    <p>{{ percentage | number:'1.2-2' }}</p>
  `
})
export class DecimalDemoComponent {
  pi = 3.14159265359;
  largeNumber = 1234567;
  percentage = 0.259;
}

PercentPipe

Format percentages:
import { Component } from '@angular/core';
import { PercentPipe } from '@angular/common';

@Component({
  selector: 'app-percent-demo',
  standalone: true,
  imports: [PercentPipe],
  template: `
    <p>{{ ratio | percent }}</p>
    <p>{{ ratio | percent:'1.2-2' }}</p>
    <p>{{ 0.5 | percent }}</p>
  `
})
export class PercentDemoComponent {
  ratio = 0.259;
}

UpperCasePipe and LowerCasePipe

Transform text case:
import { Component } from '@angular/core';
import { UpperCasePipe, LowerCasePipe, TitleCasePipe } from '@angular/common';

@Component({
  selector: 'app-case-demo',
  standalone: true,
  imports: [UpperCasePipe, LowerCasePipe, TitleCasePipe],
  template: `
    <p>{{ text | uppercase }}</p>
    <p>{{ text | lowercase }}</p>
    <p>{{ text | titlecase }}</p>
  `
})
export class CaseDemoComponent {
  text = 'hello world';
}

JsonPipe

Display objects for debugging:
import { Component } from '@angular/core';
import { JsonPipe } from '@angular/common';

@Component({
  selector: 'app-json-demo',
  standalone: true,
  imports: [JsonPipe],
  template: `
    <pre>{{ user | json }}</pre>
  `
})
export class JsonDemoComponent {
  user = {
    name: 'John Doe',
    email: 'john@example.com',
    roles: ['admin', 'user']
  };
}

SlicePipe

Extract a subset of array or string:
import { Component } from '@angular/core';
import { SlicePipe } from '@angular/common';

@Component({
  selector: 'app-slice-demo',
  standalone: true,
  imports: [SlicePipe],
  template: `
    <p>{{ text | slice:0:5 }}</p>
    <ul>
      @for (item of items | slice:0:3; track item) {
        <li>{{ item }}</li>
      }
    </ul>
  `
})
export class SliceDemoComponent {
  text = 'Hello World';
  items = ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5'];
}

AsyncPipe

Automatically subscribe to observables:
import { Component, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

interface User {
  id: number;
  name: string;
}

@Component({
  selector: 'app-async-demo',
  standalone: true,
  imports: [AsyncPipe],
  template: `
    @if (users$ | async; as users) {
      <ul>
        @for (user of users; track user.id) {
          <li>{{ user.name }}</li>
        }
      </ul>
    } @else {
      <p>Loading...</p>
    }
  `
})
export class AsyncDemoComponent {
  private http = inject(HttpClient);
  users$: Observable<User[]> = this.http.get<User[]>('/api/users');
}
The AsyncPipe automatically unsubscribes when the component is destroyed, preventing memory leaks.

Creating Custom Pipes

Implement the PipeTransform interface:
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'exponential',
  standalone: true
})
export class ExponentialPipe implements PipeTransform {
  transform(value: number, exponent: number = 1): number {
    return Math.pow(value, exponent);
  }
}

// Usage
@Component({
  selector: 'app-demo',
  standalone: true,
  imports: [ExponentialPipe],
  template: `
    <p>{{ 2 | exponential }}</p>      <!-- 2 -->
    <p>{{ 2 | exponential:3 }}</p>    <!-- 8 -->
    <p>{{ 5 | exponential:2 }}</p>    <!-- 25 -->
  `
})
export class DemoComponent { }

Pipe with Multiple Arguments

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'truncate',
  standalone: true
})
export class TruncatePipe implements PipeTransform {
  transform(value: string, limit: number = 10, suffix: string = '...'): string {
    if (!value) return '';
    
    if (value.length <= limit) {
      return value;
    }
    
    return value.substring(0, limit) + suffix;
  }
}

// Usage
@Component({
  selector: 'app-demo',
  standalone: true,
  imports: [TruncatePipe],
  template: `
    <p>{{ longText | truncate }}</p>
    <p>{{ longText | truncate:20 }}</p>
    <p>{{ longText | truncate:15:'...' }}</p>
  `
})
export class DemoComponent {
  longText = 'This is a very long text that needs truncation';
}

Impure Pipes

Pipes that detect all changes (use sparingly):
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'filter',
  standalone: true,
  pure: false // Impure pipe - executes on every change detection
})
export class FilterPipe implements PipeTransform {
  transform(items: any[], searchText: string, field: string): any[] {
    if (!items || !searchText) {
      return items;
    }
    
    searchText = searchText.toLowerCase();
    
    return items.filter(item => {
      return item[field].toLowerCase().includes(searchText);
    });
  }
}

// Usage
@Component({
  selector: 'app-search',
  standalone: true,
  imports: [FilterPipe, FormsModule],
  template: `
    <input [(ngModel)]="searchText" placeholder="Search...">
    <ul>
      @for (user of users | filter:searchText:'name'; track user.id) {
        <li>{{ user.name }}</li>
      }
    </ul>
  `
})
export class SearchComponent {
  searchText = '';
  users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
    { id: 3, name: 'Charlie' }
  ];
}
Impure pipes can impact performance because they execute on every change detection cycle. Use them carefully.

Chaining Pipes

Combine multiple pipes:
import { Component } from '@angular/core';
import { DatePipe, UpperCasePipe, SlicePipe } from '@angular/common';

@Component({
  selector: 'app-chain-demo',
  standalone: true,
  imports: [DatePipe, UpperCasePipe, SlicePipe],
  template: `
    <p>{{ today | date:'fullDate' | uppercase }}</p>
    <p>{{ text | slice:0:10 | uppercase }}</p>
    <p>{{ value | number:'1.2-2' | currency:'USD' }}</p>
  `
})
export class ChainDemoComponent {
  today = new Date();
  text = 'hello world';
  value = 123.456;
}

Complex Custom Pipe

Pipe with dependency injection:
import { Pipe, PipeTransform, inject } from '@angular/core';
import { DecimalPipe } from '@angular/common';

@Pipe({
  name: 'fileSize',
  standalone: true
})
export class FileSizePipe implements PipeTransform {
  private decimalPipe = inject(DecimalPipe);
  
  transform(bytes: number, precision: number = 2): 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));
    
    const value = bytes / Math.pow(k, i);
    const formatted = this.decimalPipe.transform(value, `1.0-${precision}`);
    
    return `${formatted} ${sizes[i]}`;
  }
}

// Usage
@Component({
  selector: 'app-demo',
  standalone: true,
  imports: [FileSizePipe],
  template: `
    <p>{{ 1024 | fileSize }}</p>        <!-- 1.00 KB -->
    <p>{{ 1048576 | fileSize }}</p>     <!-- 1.00 MB -->
    <p>{{ 1234567890 | fileSize:1 }}</p> <!-- 1.1 GB -->
  `
})
export class DemoComponent { }

Async Operations in Pipes

import { Pipe, PipeTransform, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Pipe({
  name: 'fetchUser',
  standalone: true
})
export class FetchUserPipe implements PipeTransform {
  private http = inject(HttpClient);
  
  transform(userId: number): Observable<string> {
    return this.http.get<any>(`/api/users/${userId}`).pipe(
      map(user => user.name)
    );
  }
}

// Usage with AsyncPipe
@Component({
  selector: 'app-demo',
  standalone: true,
  imports: [FetchUserPipe, AsyncPipe],
  template: `
    <p>User: {{ userId | fetchUser | async }}</p>
  `
})
export class DemoComponent {
  userId = 123;
}

Best Practices

Keep Pipes Pure

Default pure pipes for better performance

Simple Transforms

Keep transformation logic simple

Use AsyncPipe

For observables to prevent memory leaks

Avoid Side Effects

Pipes should only transform data
  1. Keep pipes pure - Unless you have a specific reason
  2. Avoid complex logic - Move to services if needed
  3. Use meaningful names - Clear and descriptive
  4. Handle null/undefined - Defensive programming
  5. Document parameters - Clear usage examples
  6. Test thoroughly - Pipes are easy to unit test

Next Steps

Templates

Learn template syntax and bindings

Directives

Extend element behavior