Angular Pipes for Data Transformation

Efficiently format and transform data in your templates

Introduction to Pipes

In Angular applications, we often need to transform data before displaying it in our templates. For example, we might want to:

Angular pipes provide a clean, reusable way to handle these transformations directly in your templates, without cluttering your component logic.

Real-World Analogy: Cooking Filters

Think of pipes like kitchen tools that transform ingredients. Just as a pasta strainer transforms boiling water and uncooked pasta into drained, ready-to-serve pasta, Angular pipes transform raw data into display-ready information. Different recipes require different tools (e.g., colanders, sieves, graters), just as different data needs different pipes.

Pipe Syntax in Templates

Using pipes in Angular templates is straightforward. You use the pipe symbol | (vertical bar) to apply a pipe to a value:

{{ value | pipeName }}

Many pipes can accept parameters, which are specified after a colon:

{{ value | pipeName:parameter1:parameter2 }}

You can also chain multiple pipes, with each pipe taking the output of the previous pipe as its input:

{{ value | pipe1 | pipe2 | pipe3 }}

Simple Example


<div>
  <p>Original: {{ product.name }}</p>
  <p>Uppercase: {{ product.name | uppercase }}</p>
  <p>Price: {{ product.price | currency:'USD' }}</p>
  <p>Purchase date: {{ product.purchaseDate | date:'medium' }}</p>
</div>
        

This example demonstrates using pipes to:

Built-in Pipes

Angular provides several built-in pipes that cover many common data transformation needs:

graph TD A[Angular Built-in Pipes] A --> B[Text Pipes
uppercase, lowercase,
titlecase] A --> C[Number Pipes
number, percent,
currency] A --> D[Date & Time
date] A --> E[Object Pipes
json, keyvalue] A --> F[Array Pipes
slice] A --> G[Other
async, i18n]

Text Transformation Pipes


<div>
  <p>Original: {{ 'angular pipes' }}</p>
  <p>uppercase: {{ 'angular pipes' | uppercase }}</p> <!-- ANGULAR PIPES -->
  <p>lowercase: {{ 'Angular Pipes' | lowercase }}</p> <!-- angular pipes -->
  <p>titlecase: {{ 'angular pipes example' | titlecase }}</p> <!-- Angular Pipes Example -->
</div>
        

Number Formatting Pipes


<div>
  <p>Original: {{ 1234.5678 }}</p>
  
  <!-- number:'minIntegerDigits.minFractionDigits-maxFractionDigits' -->
  <p>number: {{ 1234.5678 | number }}</p> <!-- 1,234.568 -->
  <p>number:'1.2-2': {{ 1234.5678 | number:'1.2-2' }}</p> <!-- 1,234.57 -->
  
  <p>percent: {{ 0.8456 | percent }}</p> <!-- 84.56% -->
  <p>percent:'2.2-2': {{ 0.8456 | percent:'2.2-2' }}</p> <!-- 84.56% -->
  
  <p>currency: {{ 49.99 | currency }}</p> <!-- $49.99 -->
  <p>currency:'EUR': {{ 49.99 | currency:'EUR' }}</p> <!-- €49.99 -->
  <p>currency:'JPY':'code': {{ 4999 | currency:'JPY':'code' }}</p> <!-- JPY 4,999 -->
</div>
        

Date Formatting Pipe

The date pipe is one of the most versatile and commonly used pipes in Angular applications.


<div>
  <!-- Assume currentDate = new Date() -->
  <p>Full date: {{ currentDate | date:'fullDate' }}</p> <!-- Monday, June 15, 2023 -->
  <p>Short date: {{ currentDate | date:'shortDate' }}</p> <!-- 6/15/23 -->
  <p>Medium date: {{ currentDate | date:'mediumDate' }}</p> <!-- Jun 15, 2023 -->
  
  <p>Time: {{ currentDate | date:'shortTime' }}</p> <!-- 1:30 PM -->
  
  <p>Custom format: {{ currentDate | date:'EEEE, MMMM d, y, h:mm a' }}</p>
  <!-- Monday, June 15, 2023, 1:30 PM -->
  
  <p>GMT date: {{ currentDate | date:'medium':'GMT' }}</p>
  <!-- Jun 15, 2023, 5:30:42 PM -->
</div>
        

The date pipe accepts many format options documented in the Angular DatePipe documentation.

Object Pipes


<div>
  <!-- Debug objects with json pipe -->
  <pre>{{ user | json }}</pre>
  
  <!-- Iterate through objects with keyvalue pipe -->
  <ul>
    <li *ngFor="let item of userProfile | keyvalue">
      {{ item.key }}: {{ item.value }}
    </li>
  </ul>
</div>
        

The json pipe is particularly useful during development for debugging purposes, as it formats JavaScript objects in a readable way.

Array Pipe: slice


<!-- Original array: ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'] -->

<!-- Display first 3 items -->
<div *ngFor="let fruit of fruits | slice:0:3">
  {{ fruit }}
</div>

<!-- Display items starting from index 2 -->
<div *ngFor="let fruit of fruits | slice:2">
  {{ fruit }}
</div>
        

Async Pipe

The async pipe is special because it subscribes to an Observable or Promise and automatically returns the latest value it emits. When the component is destroyed, the async pipe automatically unsubscribes, preventing memory leaks.


<!-- In component.ts -->
import { Component } from '@angular/core';
import { Observable, interval } from 'rxjs';
import { map, take } from 'rxjs/operators';

@Component({
  selector: 'app-async-example',
  template: `
    <div>
      <h3>Current count: {{ counter$ | async }}</h3>
      
      <div *ngIf="user$ | async as user; else loading">
        <h4>Welcome, {{ user.name }}!</h4>
        <p>Email: {{ user.email }}</p>
      </div>
      
      <ng-template #loading>
        <p>Loading user data...</p>
      </ng-template>
    </div>
  `
})
export class AsyncExampleComponent {
  // A simple Observable that emits sequential numbers
  counter$ = interval(1000).pipe(
    map(i => i + 1),
    take(10)
  );
  
  // Simulated user data fetch
  user$ = new Observable(observer => {
    setTimeout(() => {
      observer.next({ name: 'John Doe', email: 'john@example.com' });
    }, 2000);
  });
}
        

This example demonstrates:

Creating Custom Pipes

While Angular's built-in pipes cover many use cases, you'll often need to create custom pipes for application-specific transformations. Let's create a few examples to demonstrate the process.

Basic Custom Pipe

To create a custom pipe:

  1. Create a new TypeScript class
  2. Implement the PipeTransform interface
  3. Apply the @Pipe decorator
  4. Register the pipe in an Angular module

Example: File Size Pipe

Let's create a pipe that formats file sizes in bytes to a human-readable format (KB, MB, GB, etc.).


// file-size.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'fileSize'
})
export class FileSizePipe implements PipeTransform {
  transform(sizeInBytes: number, decimals: number = 2): string {
    if (sizeInBytes === 0) {
      return '0 Bytes';
    }
    
    const k = 1024;
    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
    const i = Math.floor(Math.log(sizeInBytes) / Math.log(k));
    
    return parseFloat((sizeInBytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
  }
}
        

// Register in app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FileSizePipe } from './file-size.pipe';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent,
    FileSizePipe   // Register the pipe here
  ],
  imports: [
    BrowserModule
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
        

// Using the pipe in a template
<div>
  <h3>File Downloads</h3>
  <ul>
    <li *ngFor="let file of files">
      {{ file.name }} - {{ file.size | fileSize }}
    </li>
  </ul>
</div>

// Component class
export class FilesComponent {
  files = [
    { name: 'Project Report.pdf', size: 3450000 },
    { name: 'Vacation Photos.zip', size: 147800000 },
    { name: 'Config.json', size: 1230 },
    { name: 'System Backup.iso', size: 4700000000 }
  ];
}
        

Parameterized Custom Pipe

Let's create a more complex pipe that filters an array based on a search term.


// filter.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'filter'
})
export class FilterPipe implements PipeTransform {
  transform(items: any[], searchText: string, fieldName?: string): any[] {
    if (!items) return [];
    if (!searchText) return items;
    
    searchText = searchText.toLowerCase();
    
    return items.filter(item => {
      if (fieldName) {
        // If a field name is provided, only search in that field
        const fieldValue = item[fieldName].toString().toLowerCase();
        return fieldValue.includes(searchText);
      } else {
        // Otherwise, search all string fields
        return Object.keys(item).some(key => {
          const value = item[key];
          return typeof value === 'string' && value.toLowerCase().includes(searchText);
        });
      }
    });
  }
}
        

// Using the filter pipe in a template
<div class="product-search">
  <input type="text" placeholder="Search products..." [(ngModel)]="searchTerm">
  
  <div class="filter-options">
    <label>
      <input type="radio" name="searchField" [(ngModel)]="searchField" value="">
      All Fields
    </label>
    <label>
      <input type="radio" name="searchField" [(ngModel)]="searchField" value="name">
      Name Only
    </label>
    <label>
      <input type="radio" name="searchField" [(ngModel)]="searchField" value="category">
      Category Only
    </label>
  </div>
  
  <div class="product-list">
    <div *ngFor="let product of products | filter:searchTerm:searchField" class="product-card">
      <h3>{{ product.name }}</h3>
      <p>Category: {{ product.category }}</p>
      <p>{{ product.description }}</p>
      <p>{{ product.price | currency }}</p>
    </div>
  </div>
</div>

// Component class
export class ProductSearchComponent {
  searchTerm = '';
  searchField = '';
  
  products = [
    { 
      name: 'Smartphone X', 
      category: 'Electronics', 
      description: 'Latest model with advanced features', 
      price: 999 
    },
    { 
      name: 'Running Shoes', 
      category: 'Sportswear', 
      description: 'Comfortable shoes for long-distance running', 
      price: 129 
    },
    // More products...
  ];
}
        

Pure vs Impure Pipes

Angular pipes come in two flavors: pure and impure. This distinction affects when Angular re-evaluates the pipe.

graph TB A[Pipes] A --> B[Pure Pipes
Default] A --> C[Impure Pipes] B --> D[Re-evaluated only when
input value or parameters change] C --> E[Re-evaluated on every
change detection cycle] D --> F[More performant] E --> G[Can detect changes
within objects/arrays] style B fill:#d4edda style C fill:#f8d7da style F fill:#d4edda style G fill:#f8d7da

Pure Pipes (Default)

Impure Pipes

Making a Pipe Impure


@Pipe({
  name: 'filter',
  pure: false   // Mark the pipe as impure
})
export class FilterPipe implements PipeTransform {
  // Implementation...
}
        

Performance Warning

Be cautious with impure pipes, especially when used with large data sets or complex transformations. They can significantly impact your application's performance.

For filtering large arrays, consider handling the filtering in your component class instead of using an impure pipe.

Real-World Applications

E-commerce Product Catalog

In an e-commerce application, pipes can be used to:

Financial Dashboard

For financial applications, pipes can:

Social Media Platform

In social media applications, pipes can:

Example: Relative Time Pipe


// relative-time.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'relativeTime'
})
export class RelativeTimePipe implements PipeTransform {
  transform(value: Date | string): string {
    if (!value) return '';
    
    const now = new Date();
    const date = new Date(value);
    const seconds = Math.floor((now.getTime() - date.getTime()) / 1000);
    
    // Less than a minute
    if (seconds < 60) {
      return 'just now';
    }
    
    // Less than an hour
    const minutes = Math.floor(seconds / 60);
    if (minutes < 60) {
      return minutes === 1 ? '1 minute ago' : `${minutes} minutes ago`;
    }
    
    // Less than a day
    const hours = Math.floor(minutes / 60);
    if (hours < 24) {
      return hours === 1 ? '1 hour ago' : `${hours} hours ago`;
    }
    
    // Less than a month
    const days = Math.floor(hours / 24);
    if (days < 30) {
      return days === 1 ? 'yesterday' : `${days} days ago`;
    }
    
    // Less than a year
    const months = Math.floor(days / 30);
    if (months < 12) {
      return months === 1 ? '1 month ago' : `${months} months ago`;
    }
    
    // More than a year
    const years = Math.floor(months / 12);
    return years === 1 ? '1 year ago' : `${years} years ago`;
  }
}
        

// Using the relative time pipe
<div class="social-feed">
  <div *ngFor="let post of posts" class="post">
    <div class="post-header">
      <img [src]="post.author.avatarUrl" class="avatar">
      <div>
        <h4>{{ post.author.name }}</h4>
        <small>{{ post.createdAt | relativeTime }}</small>
      </div>
    </div>
    <p>{{ post.content }}</p>
    <div class="post-actions">
      <button>Like ({{ post.likes | number }})</button>
      <button>Comment ({{ post.comments.length }})</button>
      <button>Share</button>
    </div>
  </div>
</div>
        

Best Practices

Performance Considerations

Design Considerations

When Not to Use Pipes

While pipes are powerful, they aren't always the best solution:

Practice Activities

Basic Exercise: Create a Text Truncation Pipe

Create a pipe that truncates text after a specified number of characters and adds an ellipsis.

  1. Create a new pipe named TruncatePipe
  2. Accept parameters for maximum length and custom suffix (default: "...")
  3. Handle edge cases (null values, short text that doesn't need truncation)
  4. Test the pipe with various text lengths and parameters

Intermediate Exercise: Create a Highlight Pipe

Create a pipe that highlights search terms within text by wrapping them in HTML.

  1. Create a new pipe named HighlightPipe
  2. Accept a search term and an optional CSS class for styling
  3. Find all instances of the search term (case-insensitive) and wrap them in <span> tags
  4. Make sure the pipe is safe to use with Angular's sanitization (consider using DomSanitizer)
  5. Test the pipe with various text and search terms

Advanced Exercise: Build a Custom Sorting Pipe

Create a pipe that sorts arrays of objects by any specified property and direction.

  1. Create a new pipe named SortByPipe
  2. Accept parameters for the property name and sort direction (asc/desc)
  3. Handle different data types (strings, numbers, dates)
  4. Make it work with nested properties (e.g., "user.name")
  5. Test the pipe with various data structures and sort parameters

Summary

Angular pipes are a powerful and elegant way to transform data directly in your templates. They:

By understanding both built-in and custom pipes, along with the performance implications of pure vs. impure pipes, you can effectively transform data for display in your Angular applications.

Key Takeaways

  • Use the pipe symbol | to apply transformations in templates
  • Angular provides many useful built-in pipes (date, currency, number, etc.)
  • Custom pipes can be created by implementing the PipeTransform interface
  • Pure pipes (default) only re-evaluate when inputs change by reference
  • Impure pipes re-evaluate on every change detection cycle, but can be performance-intensive
  • Consider alternatives to pipes for complex operations on large data sets

Additional Resources