Building Custom Pipes in Angular: A Comprehensive Guide to Transforming Data in Templates

Pipes in Angular are a powerful mechanism for transforming data directly within templates, enabling developers to format and manipulate displayed values without cluttering component logic. While Angular provides built-in pipes like date, uppercase, and currency, custom pipes allow you to create tailored transformations to meet specific application needs, such as formatting phone numbers, filtering lists, or sanitizing text. This guide provides a detailed, step-by-step exploration of building custom pipes in Angular, covering their purpose, creation, usage, advanced techniques (like pure vs. impure pipes), and practical examples. By the end, you’ll have a thorough understanding of how to craft reusable custom pipes to enhance your Angular applications.

This blog dives deeply into each concept, ensuring clarity and practical applicability while maintaining readability. We’ll incorporate internal links to related resources and provide actionable code examples. Let’s explore how to build custom pipes in Angular.


What are Angular Pipes?

An Angular pipe is a function that transforms input data into a desired output format for display in templates. Pipes are applied using the | operator in Angular templates, making them concise and declarative. For example, the built-in uppercase pipe transforms text to uppercase:

{ { 'hello world' | uppercase }}

Custom pipes extend this functionality by allowing you to define your own transformation logic. They are ideal for:

  • Formatting data (e.g., custom date formats, currency symbols).
  • Filtering or sorting collections (e.g., search results).
  • Sanitizing or processing user input (e.g., truncating text).
  • Reusing transformation logic across components.

Custom pipes are defined using the @Pipe decorator and can be pure (default, stateless) or impure (stateful, re-evaluated on every change detection cycle). For a foundational overview of pipes, see Angular Pipes.


Setting Up an Angular Project

To build custom pipes, we need an Angular project. Let’s set it up.

Step 1: Create a New Angular Project

Use the Angular CLI to create a project:

ng new custom-pipe-demo

Navigate to the project directory:

cd custom-pipe-demo

This generates a new Angular project. For more details, see Angular: Create a New Project.

Step 2: Verify the Module

Ensure app.module.ts includes BrowserModule, which imports CommonModule (providing built-in pipes):

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  bootstrap: [AppComponent]
})
export class AppModule {}

Step 3: Generate a Component

Create a component to test the custom pipe:

ng generate component pipe-demo

This creates a pipe-demo component. For more on components, see Angular Component.


Creating a Basic Custom Pipe

Let’s create a custom pipe to format a string by capitalizing the first letter of each word.

Step 1: Generate a Custom Pipe

Use the Angular CLI to create a pipe:

ng generate pipe title-case

This generates title-case.pipe.ts and updates app.module.ts:

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

@Pipe({
  name: 'titleCase'
})
export class TitleCasePipe implements PipeTransform {
  transform(value: any): any {
    return null;
  }
}

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { PipeDemoComponent } from './pipe-demo/pipe-demo.component';
import { TitleCasePipe } from './title-case.pipe';

@NgModule({
  declarations: [AppComponent, PipeDemoComponent, TitleCasePipe],
  imports: [BrowserModule],
  bootstrap: [AppComponent]
})
export class AppModule {}
  • The @Pipe decorator defines the pipe with a name (used in templates).
  • The PipeTransform interface requires a transform method to define the transformation logic.

Step 2: Implement the Pipe Logic

Update title-case.pipe.ts to capitalize each word:

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

@Pipe({
  name: 'titleCase'
})
export class TitleCasePipe implements PipeTransform {
  transform(value: string): string {
    if (!value) return '';

    return value
      .toLowerCase()
      .split(' ')
      .map(word => word.charAt(0).toUpperCase() + word.slice(1))
      .join(' ');
  }
}
  • The transform method takes a string input and returns a transformed string.
  • It handles empty inputs, splits the string into words, capitalizes the first letter of each, and joins them back.

Step 3: Use the Pipe in a Template

Update pipe-demo.component.ts:

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

@Component({
  selector: 'app-pipe-demo',
  templateUrl: './pipe-demo.component.html'
})
export class PipeDemoComponent {
  text = 'welcome to angular pipes';
}

In pipe-demo.component.html:

Title Case Pipe
Original: { { text }}
Transformed: { { text | titleCase }}

Output:

Original: welcome to angular pipes
Transformed: Welcome To Angular Pipes

Run ng serve to see the pipe in action.


Creating a Parameterized Custom Pipe

Pipes can accept parameters to customize their behavior. Let’s create a pipe to truncate text with an optional length parameter.

Step 1: Generate a Truncate Pipe

ng generate pipe truncate

Step 2: Implement the Truncate Logic

In truncate.pipe.ts:

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

@Pipe({
  name: 'truncate'
})
export class TruncatePipe implements PipeTransform {
  transform(value: string, limit: number = 20, ellipsis: string = '...'): string {
    if (!value) return '';

    return value.length > limit ? value.substring(0, limit) + ellipsis : value;
  }
}
  • The transform method accepts:
    • value: The input string.
    • limit: Maximum length (default: 20).
    • ellipsis: Suffix for truncated text (default: '...').
  • If the string exceeds limit, it’s truncated and appended with ellipsis.

Step 3: Use the Parameterized Pipe

Update pipe-demo.component.ts:

text = 'This is a long description that needs truncation for display purposes.';

In pipe-demo.component.html:

Truncate Pipe
Original: { { text }}
Default: { { text | truncate }}
Custom Limit: { { text | truncate:10 }}
Custom Ellipsis: { { text | truncate:10:'>>>' }}

Output:

Original: This is a long description that needs truncation for display purposes.
Default: This is a long descriptio...
Custom Limit: This is a ...
Custom Ellipsis: This is a >>>

Parameters are passed using : in the template (e.g., truncate:10:'>>>').


Pure vs. Impure Pipes

Pipes are pure by default, meaning they only re-evaluate when their input value or parameters change (based on reference comparison). Impure pipes re-evaluate on every change detection cycle, useful for stateful transformations like filtering dynamic arrays.

Creating an Impure Filter Pipe

Let’s create a pipe to filter an array of objects based on a search term.

Step 1: Generate a Filter Pipe

ng generate pipe filter

Step 2: Implement the Filter Logic

In filter.pipe.ts:

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

@Pipe({
  name: 'filter',
  pure: false // Impure pipe
})
export class FilterPipe implements PipeTransform {
  transform(items: any[], searchTerm: string, property: string): any[] {
    if (!items || !searchTerm || !property) return items;

    return items.filter(item =>
      item[property]?.toLowerCase().includes(searchTerm.toLowerCase())
    );
  }
}
  • The pure: false setting makes the pipe impure, re-evaluating on every change detection cycle.
  • The transform method filters items based on searchTerm for a given property.

Step 3: Use the Filter Pipe

Update pipe-demo.component.ts:

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

@Component({
  selector: 'app-pipe-demo',
  templateUrl: './pipe-demo.component.html'
})
export class PipeDemoComponent {
  users = [
    { name: 'John Doe', email: 'john@example.com' },
    { name: 'Jane Smith', email: 'jane@example.com' },
    { name: 'Bob Johnson', email: 'bob@example.com' }
  ];
  searchTerm = '';
}

In pipe-demo.component.html:

Filter Pipe


  
    { { user.name }} ({ { user.email }})
  • The [(ngModel)] binds the input to searchTerm.
  • The filter pipe filters users by name based on searchTerm.

Update app.module.ts to include FormsModule for ngModel:

import { FormsModule } from '@angular/forms';

@NgModule({
  imports: [BrowserModule, FormsModule],
  ...
})
export class AppModule {}

Note: Impure pipes can impact performance, as they run frequently. Use pure pipes when possible or optimize impure pipes by minimizing computation. For more, see Use Pure vs Impure Pipes.


Chaining Custom Pipes

Pipes can be chained to apply multiple transformations. Let’s combine titleCase and truncate:

In pipe-demo.component.html:

Chained Pipes
{ { text | titleCase | truncate:15 }}

Output:

This Is A Long De...

The titleCase pipe capitalizes words, then truncate limits the length. For more on built-in pipes, see Angular Pipes.


Advanced Use Case: Dynamic Sorting Pipe

Let’s create an impure pipe to sort an array dynamically by a property and direction.

Step 1: Generate a Sort Pipe

ng generate pipe sort

Step 2: Implement the Sort Logic

In sort.pipe.ts:

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

@Pipe({
  name: 'sort',
  pure: false
})
export class SortPipe implements PipeTransform {
  transform(items: any[], property: string, direction: 'asc' | 'desc' = 'asc'): any[] {
    if (!items || !property) return items;

    return [...items].sort((a, b) => {
      const valueA = a[property]?.toString().toLowerCase();
      const valueB = b[property]?.toString().toLowerCase();
      if (valueA < valueB) return direction === 'asc' ? -1 : 1;
      if (valueA > valueB) return direction === 'asc' ? 1 : -1;
      return 0;
    });
  }
}
  • The pipe sorts items by property in asc (ascending) or desc (descending) order.
  • A copy of items ([...items]) prevents mutating the original array.
  • The pipe is impure to handle dynamic array changes.

Step 3: Use the Sort Pipe

Update pipe-demo.component.ts:

sortProperty = 'name';
sortDirection: 'asc' | 'desc' = 'asc';

toggleSortDirection() {
  this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
}

In pipe-demo.component.html:

Sort Pipe
Toggle Sort: { { sortDirection }}

  
    { { user.name }} ({ { user.email }})
  • The button toggles sortDirection.
  • The sort pipe reorders users by name in the specified direction.

Testing Custom Pipes

Testing ensures pipes work as expected. Let’s test the TitleCasePipe.

Step 1: Generate a Test File

The CLI generates title-case.pipe.spec.ts. Update it:

import { TitleCasePipe } from './title-case.pipe';

describe('TitleCasePipe', () => {
  let pipe: TitleCasePipe;

  beforeEach(() => {
    pipe = new TitleCasePipe();
  });

  it('should create an instance', () => {
    expect(pipe).toBeTruthy();
  });

  it('should transform text to title case', () => {
    expect(pipe.transform('hello world')).toBe('Hello World');
  });

  it('should handle empty strings', () => {
    expect(pipe.transform('')).toBe('');
  });

  it('should handle single words', () => {
    expect(pipe.transform('angular')).toBe('Angular');
  });
});

Step 2: Run Tests

Execute tests:

ng test

This ensures the pipe handles various inputs correctly. For more on testing, see Angular Testing.


FAQs

What is a custom pipe in Angular?

A custom pipe is a user-defined transformation function, created with the @Pipe decorator, to format or manipulate data in Angular templates.

What’s the difference between pure and impure pipes?

Pure pipes only re-evaluate when inputs change (reference comparison), optimizing performance. Impure pipes re-evaluate on every change detection cycle, suitable for stateful transformations.

How do I pass parameters to a custom pipe?

Pass parameters using the : operator in the template, e.g., { { value | pipeName:param1:param2 }}.

Can I chain multiple pipes?

Yes, pipes can be chained by applying them sequentially, e.g., { { value | pipe1 | pipe2 }}, where each pipe transforms the output of the previous one.

How do I test a custom pipe?

Create a test file with Jasmine or Jest, instantiate the pipe, and test its transform method with various inputs to ensure correct behavior.


Conclusion

Building custom pipes in Angular empowers developers to create reusable, declarative data transformations that enhance template readability and maintainability. This guide covered creating basic and parameterized pipes, understanding pure vs. impure pipes, chaining pipes, and implementing advanced sorting functionality, providing a solid foundation for custom pipe development.

To deepen your knowledge, explore related topics like Use Async Pipe for Data for observable integration, Create Reusable Components for modular UI, or Test Components with Jasmine for robust testing. With custom pipes, you can craft tailored, efficient Angular applications that meet your specific needs.