DEV Community

Tianya School
Tianya School

Posted on

Angular Pipes Data Transformation and Formatting

Today, let’s dive into Angular Pipes, a super handy tool in Angular development for transforming and formatting data effortlessly. Whether you’re formatting dates, handling currencies, filtering lists, or tackling complex data transformations, Pipes keep your template code clean and readable.

What Are Angular Pipes?

Pipes in Angular are tools for transforming data in templates. Think of them as “filters” that take raw data and output it in your desired format. For example, turning 1234.567 into $1,234.57 or 2025-07-02 into July 2, 2025 is a breeze with Pipes. Their key features include:

  • Declarative: Used directly in templates for concise code.
  • Reusable: A single Pipe can be used across multiple components.
  • Built-in and Custom: Angular provides a set of built-in Pipes, and you can create your own.

Pipes are applied in templates with the syntax {{ value | pipeName:arg1:arg2 }}, where | is the Pipe operator, followed by the Pipe name and optional arguments. We’ll start with Angular’s built-in Pipes and progress to custom Pipes and advanced scenarios.

Environment Setup

To use Pipes, set up an Angular development environment with Node.js (18.x recommended) and Angular CLI (version 18.x at the time of writing).

Create a new project:

npm install -g @angular/cli
ng new angular-pipes-demo
cd angular-pipes-demo
Enter fullscreen mode Exit fullscreen mode

Choose default settings (CSS, no SSR). The project structure looks like this:

angular-pipes-demo/
├── src/
│   ├── app/
│   │   ├── app.component.ts
│   │   ├── app.module.ts
│   ├── main.ts
│   ├── index.html
├── angular.json
├── package.json
Enter fullscreen mode Exit fullscreen mode

Run ng serve and visit localhost:4200 to see the default page. We’ll experiment with Pipes in app.component.

Built-in Pipes: Ready-to-Use Tools

Angular provides a range of built-in Pipes for common data transformation tasks, such as formatting dates, numbers, and strings. Let’s explore a few popular ones.

DatePipe: Formatting Dates

DatePipe transforms date objects or strings into specified formats. Update app.component.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <h1>DatePipe Demo</h1>
    <p>Today: {{ today | date }}</p>
    <p>Short: {{ today | date:'short' }}</p>
    <p>Custom: {{ today | date:'MMMM d, yyyy' }}</p>
  `,
})
export class AppComponent {
  today = new Date();
}
Enter fullscreen mode Exit fullscreen mode

Run ng serve. The page displays:

  • Today: Jul 2, 2025
  • Short: 7/2/25, 12:00 AM
  • Custom: July 2, 2025

date accepts format strings like short, medium, full, or custom formats (MMMM d, yyyy). Try dynamic dates:

template: `
  <input type="date" [(ngModel)]="selectedDate">
  <p>Formatted: {{ selectedDate | date:'fullDate' }}</p>
`
Enter fullscreen mode Exit fullscreen mode
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-root',
  template: `...`,
  standalone: true,
  imports: [FormsModule]
})
export class AppComponent {
  selectedDate = new Date();
}
Enter fullscreen mode Exit fullscreen mode

Select a date, and the page updates with formats like “Wednesday, July 2, 2025”.

CurrencyPipe: Formatting Currency

CurrencyPipe converts numbers into currency formats, supporting various symbols and locales. Update app.component.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <h1>CurrencyPipe Demo</h1>
    <p>USD: {{ price | currency }}</p>
    <p>EUR: {{ price | currency:'EUR' }}</p>
    <p>Custom: {{ price | currency:'JPY':'symbol':'1.0-0' }}</p>
  `,
})
export class AppComponent {
  price = 1234.567;
}
Enter fullscreen mode Exit fullscreen mode

Output:

  • USD: $1,234.57
  • EUR: €1,234.57
  • Custom: ¥1,235

currency takes three parameters:

  • Currency code (USD, EUR, JPY).
  • Display type (symbol, $, code).
  • Digit format (1.0-0 for 1 integer digit, 0 decimal digits).

Try dynamic input:

template: `
  <input type="number" [(ngModel)]="price">
  <p>{{ price | currency:'USD':'symbol' }}</p>
`
Enter fullscreen mode Exit fullscreen mode
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-root',
  template: `...`,
  standalone: true,
  imports: [FormsModule]
})
export class AppComponent {
  price = 1234.567;
}
Enter fullscreen mode Exit fullscreen mode

Enter a price, and it displays formatted in real-time.

DecimalPipe: Number Formatting

DecimalPipe controls decimal places and thousand separators:

@Component({
  selector: 'app-root',
  template: `
    <h1>DecimalPipe Demo</h1>
    <p>Default: {{ number | number }}</p>
    <p>Two decimals: {{ number | number:'1.2-2' }}</p>
    <p>No fraction: {{ number | number:'1.0-0' }}</p>
  `,
})
export class AppComponent {
  number = 1234.5678;
}
Enter fullscreen mode Exit fullscreen mode

Output:

  • Default: 1,234.568
  • Two decimals: 1,234.57
  • No fraction: 1,235

number:'1.2-2' means:

  • 1: Minimum 1 integer digit.
  • 2-2: Exactly 2 decimal digits.

PercentPipe: Percentage Formatting

PercentPipe converts decimals to percentages:

@Component({
  selector: 'app-root',
  template: `
    <h1>PercentPipe Demo</h1>
    <p>Default: {{ ratio | percent }}</p>
    <p>Three decimals: {{ ratio | percent:'1.3-3' }}</p>
  `,
})
export class AppComponent {
  ratio = 0.456;
}
Enter fullscreen mode Exit fullscreen mode

Output:

  • Default: 46%
  • Three decimals: 45.600%

JsonPipe: Debugging Objects

JsonPipe converts objects to JSON strings, great for debugging:

@Component({
  selector: 'app-root',
  template: `
    <h1>JsonPipe Demo</h1>
    <pre>{{ user | json }}</pre>
  `,
})
export class AppComponent {
  user = { name: 'Alice', age: 30, city: 'New York' };
}
Enter fullscreen mode Exit fullscreen mode

Output:

{
  "name": "Alice",
  "age": 30,
  "city": "New York"
}
Enter fullscreen mode Exit fullscreen mode

SlicePipe: Slicing Arrays or Strings

SlicePipe extracts parts of arrays or strings:

@Component({
  selector: 'app-root',
  template: `
    <h1>SlicePipe Demo</h1>
    <p>String: {{ text | slice:0:5 }}</p>
    <ul>
      <li *ngFor="let item of items | slice:1:3">{{ item }}</li>
    </ul>
  `,
})
export class AppComponent {
  text = 'Hello, Angular!';
  items = ['Apple', 'Banana', 'Cherry', 'Date'];
}
Enter fullscreen mode Exit fullscreen mode

Output:

  • String: Hello
  • List: Banana, Cherry

slice:0:5 extracts from index 0 to 5 (exclusive), applicable to both arrays and strings.

Chained Pipes

Pipes can be chained, passing the output of one Pipe to the next. Try converting a string to uppercase and slicing:

@Component({
  selector: 'app-root',
  template: `
    <h1>Chained Pipes</h1>
    <p>{{ text | uppercase | slice:0:5 }}</p>
  `,
})
export class AppComponent {
  text = 'hello, angular!';
}
Enter fullscreen mode Exit fullscreen mode

Output: HELLO

uppercase converts to HELLO, ANGULAR!, then slice:0:5 extracts HELLO. Chained Pipes simplify complex transformations.

Custom Pipes: Handling Complex Logic

Built-in Pipes are great, but real projects often need custom logic, like filtering lists or formatting specific data. Let’s create custom Pipes.

Creating a Simple Custom Pipe

Generate a Pipe:

ng generate pipe filter
Enter fullscreen mode Exit fullscreen mode

This creates app/filter.pipe.ts:

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

@Pipe({
  name: 'filter',
  standalone: true
})
export class FilterPipe implements PipeTransform {
  transform(items: any[], term: string): any[] {
    if (!items || !term) return items;
    return items.filter(item => item.toLowerCase().includes(term.toLowerCase()));
  }
}
Enter fullscreen mode Exit fullscreen mode

Use in app.component.ts:

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { FilterPipe } from './filter.pipe';

@Component({
  selector: 'app-root',
  template: `
    <h1>Filter Pipe</h1>
    <input type="text" [(ngModel)]="searchTerm">
    <ul>
      <li *ngFor="let item of items | filter:searchTerm">{{ item }}</li>
    </ul>
  `,
  standalone: true,
  imports: [FormsModule, FilterPipe]
})
export class AppComponent {
  searchTerm = '';
  items = ['Apple', 'Banana', 'Cherry', 'Date'];
}
Enter fullscreen mode Exit fullscreen mode

Enter “ap” to filter the list to Apple. The filter Pipe dynamically filters the array based on input.

Custom Pipe with Parameters

Update filter.pipe.ts to support case sensitivity:

@Pipe({
  name: 'filter',
  standalone: true
})
export class FilterPipe implements PipeTransform {
  transform(items: any[], term: string, caseSensitive: boolean = false): any[] {
    if (!items || !term) return items;
    return items.filter(item => {
      const search = caseSensitive ? term : term.toLowerCase();
      const value = caseSensitive ? item : item.toLowerCase();
      return value.includes(search);
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Update template:

template: `
  <h1>Filter Pipe</h1>
  <input type="text" [(ngModel)]="searchTerm">
  <label><input type="checkbox" [(ngModel)]="caseSensitive"> Case Sensitive</label>
  <ul>
    <li *ngFor="let item of items | filter:searchTerm:caseSensitive">{{ item }}</li>
  </ul>
`
Enter fullscreen mode Exit fullscreen mode
export class AppComponent {
  searchTerm = '';
  caseSensitive = false;
  items = ['Apple', 'banana', 'Cherry', 'Date'];
}
Enter fullscreen mode Exit fullscreen mode

Check “Case Sensitive” and enter “Apple” to match only Apple. Unchecked, it ignores case.

Pure vs. Impure Pipes

By default, Pipes are “pure,” only running when inputs change. For dynamic data (e.g., array mutations), use an “impure” Pipe. Update filter.pipe.ts:

@Pipe({
  name: 'filter',
  standalone: true,
  pure: false
})
export class FilterPipe implements PipeTransform {
  transform(items: any[], term: string): any[] {
    console.log('FilterPipe triggered');
    if (!items || !term) return items;
    return items.filter(item => item.toLowerCase().includes(term.toLowerCase()));
  }
}
Enter fullscreen mode Exit fullscreen mode
@Component({
  selector: 'app-root',
  template: `
    <h1>Impure Pipe</h1>
    <input type="text" [(ngModel)]="searchTerm">
    <button (click)="addItem()">Add Item</button>
    <ul>
      <li *ngFor="let item of items | filter:searchTerm">{{ item }}</li>
    </ul>
  `,
  standalone: true,
  imports: [FormsModule, FilterPipe]
})
export class AppComponent {
  searchTerm = '';
  items = ['Apple', 'Banana'];

  addItem() {
    this.items.push('New Item');
  }
}
Enter fullscreen mode Exit fullscreen mode

Click “Add Item” to mutate items, triggering the filter Pipe (console logs confirm). Pure Pipes don’t re-run for mutations, ideal for static data; impure Pipes handle dynamic changes but have higher performance costs.

Complex Scenario: Formatting Complex Objects

Create a Pipe to format user objects. Generate user-format.pipe.ts:

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

@Pipe({
  name: 'userFormat',
  standalone: true
})
export class UserFormatPipe implements PipeTransform {
  transform(user: { name: string, age: number, city: string }, format: 'short' | 'full' = 'short'): string {
    if (!user) return '';
    if (format === 'short') {
      return `${user.name}, ${user.age}`;
    }
    return `${user.name}, ${user.age} years old, from ${user.city}`;
  }
}
Enter fullscreen mode Exit fullscreen mode

Use in app.component.ts:

import { Component } from '@angular/core';
import { UserFormatPipe } from './user-format.pipe';

@Component({
  selector: 'app-root',
  template: `
    <h1>User Format Pipe</h1>
    <p>Short: {{ user | userFormat }}</p>
    <p>Full: {{ user | userFormat:'full' }}</p>
  `,
  standalone: true,
  imports: [UserFormatPipe]
})
export class AppComponent {
  user = { name: 'Alice', age: 30, city: 'New York' };
}
Enter fullscreen mode Exit fullscreen mode

Output:

  • Short: Alice, 30
  • Full: Alice, 30 years old, from New York

Async Data: AsyncPipe

AsyncPipe handles Observables or Promises, automatically subscribing and unsubscribing. Update app.component.ts:

import { Component } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';

@Component({
  selector: 'app-root',
  template: `
    <h1>AsyncPipe Demo</h1>
    <p>Data: {{ data$ | async }}</p>
  `,
  standalone: true,
  imports: [AsyncPipe]
})
export class AppComponent {
  data$ = of('Hello from Observable!').pipe(delay(2000));
}
Enter fullscreen mode Exit fullscreen mode

After 2 seconds, it displays “Hello from Observable!”. AsyncPipe subscribes to data$ and unsubscribes on component destruction, preventing memory leaks.

Try a Promise:

@Component({
  selector: 'app-root',
  template: `
    <h1>AsyncPipe with Promise</h1>
    <p>Data: {{ promise | async }}</p>
  `,
  standalone: true,
  imports: [AsyncPipe]
})
export class AppComponent {
  promise = new Promise(resolve => {
    setTimeout(() => resolve('Hello from Promise!'), 2000);
  });
}
Enter fullscreen mode Exit fullscreen mode

Similar effect: displays the result after 2 seconds.

With HTTP Requests

Use HttpClient for data fetching:

ng add @angular/common
Enter fullscreen mode Exit fullscreen mode

Update app.component.ts:

import { Component } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-root',
  template: `
    <h1>AsyncPipe with HTTP</h1>
    <ul>
      <li *ngFor="let user of users$ | async">{{ user.name }}</li>
    </ul>
  `,
  standalone: true,
  imports: [AsyncPipe]
})
export class AppComponent {
  users$ = this.http.get('https://jsonplaceholder.typicode.com/users');
  constructor(private http: HttpClient) {}
}
Enter fullscreen mode Exit fullscreen mode

Run it to display a user list. AsyncPipe subscribes to the HTTP Observable and renders the data.

Combining NgFor and Pipes

Pipes often pair with *ngFor for dynamic lists. Combine filter and AsyncPipe:

import { Component } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { FilterPipe } from './filter.pipe';
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';

@Component({
  selector: 'app-root',
  template: `
    <h1>Filter with AsyncPipe</h1>
    <input type="text" [(ngModel)]="searchTerm">
    <ul>
      <li *ngFor="let item of items$ | async | filter:searchTerm">{{ item }}</li>
    </ul>
  `,
  standalone: true,
  imports: [AsyncPipe, FormsModule, FilterPipe]
})
export class AppComponent {
  searchTerm = '';
  items$ = of(['Apple', 'Banana', 'Cherry', 'Date']).pipe(delay(1000));
}
Enter fullscreen mode Exit fullscreen mode

After 1 second, the list loads. Enter “ap” to filter to Apple. AsyncPipe handles the Observable, and filter dynamically filters the list.

Internationalization (i18n) with Pipes

Pipes are great for internationalization, as DatePipe and CurrencyPipe support locale settings. Update app.component.ts:

@Component({
  selector: 'app-root',
  template: `
    <h1>i18n with Pipes</h1>
    <select [(ngModel)]="locale">
      <option value="en-US">English (US)</option>
      <option value="fr-FR">French</option>
    </select>
    <p>Date: {{ today | date:'fullDate':undefined:locale }}</p>
    <p>Currency: {{ price | currency:'USD':'symbol':undefined:locale }}</p>
  `,
  standalone: true,
  imports: [FormsModule]
})
export class AppComponent {
  today = new Date();
  price = 1234.567;
  locale = 'en-US';
}
Enter fullscreen mode Exit fullscreen mode

Switch to French: the date shows “mercredi 2 juillet 2025”, and currency shows “1 234,57 $US”. The locale parameter dynamically adjusts formatting.

Performance: Pure Pipes and Change Detection

Pure Pipes run only when inputs change, optimizing performance. Update user-format.pipe.ts as a pure Pipe:

@Pipe({
  name: 'userFormat',
  standalone: true,
  pure: true
})
export class UserFormatPipe implements PipeTransform {
  transform(user: { name: string, age: number }, format: 'short' | 'full' = 'short'): string {
    console.log('UserFormatPipe triggered');
    return format === 'short' ? `${user.name}, ${user.age}` : `${user.name}, ${user.age} years old`;
  }
}
Enter fullscreen mode Exit fullscreen mode
@Component({
  selector: 'app-root',
  template: `
    <h1>Pure Pipe</h1>
    <button (click)="update()">Update</button>
    <p>{{ user | userFormat:'full' }}</p>
  `,
  standalone: true,
  imports: [UserFormatPipe]
})
export class AppComponent {
  user = { name: 'Alice', age: 30 };

  update() {
    this.user = { ...this.user }; // Shallow copy
  }
}
Enter fullscreen mode Exit fullscreen mode

Click the button: the Pipe doesn’t re-run (no console logs) because the user reference hasn’t changed. Set pure: false to make it impure, and it runs on every click.

Complex Scenario: Chained Custom Pipes

Create a Pipe to convert numbers to words, then uppercase them:

ng generate pipe numberToWords
Enter fullscreen mode Exit fullscreen mode

number-to-words.pipe.ts:

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

@Pipe({
  name: 'numberToWords',
  standalone: true
})
export class NumberToWordsPipe implements PipeTransform {
  private units = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'];

  transform(value: number): string {
    if (value < 0 || value > 9) return 'Invalid';
    return this.units[value];
  }
}
Enter fullscreen mode Exit fullscreen mode

Use in template:

@Component({
  selector: 'app-root',
  template: `
    <h1>Chained Custom Pipes</h1>
    <input type="number" [(ngModel)]="number">
    <p>{{ number | numberToWords | uppercase }}</p>
  `,
  standalone: true,
  imports: [FormsModule, NumberToWordsPipe]
})
export class AppComponent {
  number = 5;
}
Enter fullscreen mode Exit fullscreen mode

Enter 5 to display FIVE. numberToWords converts 5 to five, and uppercase transforms it to FIVE.

Conclusion (Technical Details)

Angular Pipes are powerful for data transformation. Built-in Pipes (DatePipe, CurrencyPipe, etc.) handle common formats, custom Pipes tackle complex logic, and AsyncPipe manages async data. The examples showed:

  • Built-in Pipes for dates, currencies, numbers, percentages, JSON, and slicing.
  • Custom Pipes for list filtering and object formatting.
  • Pure/impure Pipes and their performance impact.
  • Async data and internationalization support.
  • Chained Pipes and dynamic interactions.

Run these examples, experiment with formats and filtering, and experience the flexibility of Angular Pipes!

Top comments (0)