How to Create and Register a Custom Pipe in Angular 17: A Complete Guide
You register a custom pipe in Angular by decorating a class with @Pipe, implementing the PipeTransform interface, and either declaring it in an NgModule or importing it as a standalone pipe directly into your component.
Angular 17 provides a powerful transformation mechanism through custom pipes, allowing you to format data directly within templates without cluttering component logic. Whether you are truncating strings, formatting currencies, or transforming dates, understanding the proper custom pipe in Angular setup is essential for clean, reusable code. This guide walks through the exact implementation details found in the Angular source code, including the PipeTransform interface and PipeDecorator metadata.
Creating the Custom Pipe Class
Every custom pipe in Angular starts with a class that implements the PipeTransform interface. This contract, defined in packages/core/src/change_detection/pipe_transform.ts, requires a single transform method that accepts an input value and optional arguments, returning the transformed result.
Implementing PipeTransform
The transform method signature follows this pattern:
interface PipeTransform {
transform(value: any, ...args: any[]): any;
}
Here is a practical implementation of a TruncatePipe that limits string length, based on the example in packages/examples/core/ts/pipes/truncate.ts:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'truncate',
pure: true
})
export class TruncatePipe implements PipeTransform {
transform(value: string, limit: number = 10): string {
if (!value) return '';
return value.length > limit ? value.slice(0, limit) + '…' : value;
}
}
The @Pipe Decorator Metadata
The @Pipe decorator, defined in packages/core/src/metadata/directives.ts within the PipeDecorator interface, registers your class with Angular's compiler. Key metadata properties include:
- name: The string identifier used in templates (e.g.,
{{ value | truncate }}) - pure: Boolean indicating whether the pipe is pure (default
true). Pure pipes memoize results and only re-evaluate when inputs change; impure pipes run on every change detection cycle.
Registering Custom Pipes in Angular Modules
For traditional module-based applications, you must declare the pipe in an NgModule to make it available to components within that module.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TruncatePipe } from './truncate.pipe';
@NgModule({
declarations: [TruncatePipe],
imports: [CommonModule],
exports: [TruncatePipe]
})
export class SharedPipesModule {}
When you export the pipe, other modules can import SharedPipesModule and use TruncatePipe in their templates without redeclaring it. The declarations array is the essential step for registering the pipe within the module's scope.
Using Standalone Pipes in Angular 17
Angular 14 introduced standalone components, directives, and pipes, which remain the recommended approach in Angular 17. This eliminates the need for NgModule boilerplate when registering a custom pipe.
The standalone Flag
Add standalone: true to the @Pipe metadata, as supported by the PipeDecorator interface in packages/core/src/metadata/directives.ts:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'standaloneTruncate',
standalone: true,
pure: true
})
export class StandaloneTruncatePipe implements PipeTransform {
transform(value: string, limit: number = 10): string {
if (!value) return '';
return value.length > limit ? value.slice(0, limit) + '…' : value;
}
}
Importing Directly into Components
Import the standalone pipe directly into the component that uses it:
import { Component } from '@angular/core';
import { StandaloneTruncatePipe } from './standalone-truncate.pipe';
@Component({
selector: 'app-demo',
template: `<p>{{ message | standaloneTruncate:20 }}</p>`,
imports: [StandaloneTruncatePipe]
})
export class DemoComponent {
message = 'This is a very long message that needs truncation';
}
This approach leverages Angular's Ivy compiler to generate a PipeDef object, defined in packages/core/src/render3/interfaces/definition.ts, which stores the pipe's factory and transformation logic without module intermediaries.
How Angular Discovers and Compiles Pipes
When Angular compiles your application, the Ivy renderer processes the @Pipe metadata and creates a PipeDef structure. This definition, detailed in packages/core/src/render3/interfaces/definition.ts, contains:
- type: The pipe class reference
- factory: A factory function for creating pipe instances
- pure: Boolean indicating purity for change detection optimization
At runtime, the change detection engine invokes the transform method defined in the PipeTransform interface whenever the pipe's inputs change (for pure pipes) or on every cycle (for impure pipes).
Summary
- Create a class implementing
PipeTransformfrom@angular/coreand decorate it with@Pipe, specifying a unique name in the metadata. - Register module-based pipes in the
declarationsarray (andexportsfor sharing) within anNgModule. - Use standalone pipes in Angular 17 by setting
standalone: trueand importing directly into componentimportsarrays for better tree-shaking and reduced boilerplate. - Reference the Angular source code in
packages/core/src/metadata/directives.tsfor decorator metadata andpackages/core/src/change_detection/pipe_transform.tsfor the transformation contract.
Frequently Asked Questions
What is the difference between pure and impure pipes?
Pure pipes, the default in Angular, memoize results and only re-execute when the input reference changes. Impure pipes, marked with pure: false in the @Pipe decorator, run on every change detection cycle. Use impure pipes sparingly, as they can significantly impact performance.
Can I use a custom pipe in multiple modules?
Yes. For module-based architectures, export the pipe from its declaring module and import that module wherever the pipe is needed. Alternatively, convert the pipe to a standalone pipe with standalone: true and import it directly into any component that requires it, bypassing module boundaries entirely.
How do I test a custom pipe in Angular?
Test pipes by instantiating the class directly and calling the transform method, as pipes have no dependencies on the DOM. For example: expect(new TruncatePipe().transform('hello world', 5)).toBe('hello…');. For integration testing, declare the pipe in a test module or import it as a standalone pipe in a TestBed configuration.
Are standalone pipes better than module-based pipes?
Standalone pipes reduce boilerplate by eliminating the need for NgModule declarations. They are the recommended approach in Angular 17 for new development, offering better tree-shaking and simpler dependency management. However, module-based pipes remain fully supported for maintaining existing applications or when working with legacy module architectures.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →