Transform Data with Angular Pipes
Pipes turn raw values into display-ready output inside the template, and reuse better than a formatting method per component
Most applications follow a simple flow:
- Fetch data from a server — an API call, or a WebSocket for real-time updates.
- Transform the data — for example, turn
2020-06-24T09:00:00.000Z(ISO format) into something readable likeJun 24, 2020. - Display it in the UI.
Pipes handle step 2 — transforming data before it reaches the user.
What is a pipe?
A pipe is a function that takes an input and returns a transformed output.
Servers often exchange dates as ISO strings like "2020-06-24T09:00:00.000Z" (June 24, 2020, 5:00 PM Singapore time). Users shouldn't see raw ISO strings. We transform them to formats like Jun 24, 2020, 5:00:00 PM.
In Angular you can:
- Write a function that accepts a date and returns a formatted string.
- Write a pipe that does the same.
Pipes are easier to reuse across many templates that display dates.
Using pipes
Angular ships common pipes in @angular/common. You can also write custom pipes for project-specific needs.
A pipe accepts input and returns output. Suppose we have a now property:
export class PipeExampleComponent implements OnInit { now = '2020-06-24T09:00:00.000Z';}Display it with the built-in DatePipe:
<div>{{ now | date }}</div>// Jun 24, 2020<div>{{ now | date:'medium'}}</div>// Jun 24, 2020, 5:00:00 PMInside {{ }}, the pipe operator | separates the value from the pipe name:
{{ interpolated_value | pipe_name }}Pipe parameters
Pass parameters after colons:
{{ interpolated_value | pipe_name:parameter1:parameter2:...:parameterN }}There's no limit on parameter count.
Chaining pipes
Chain multiple pipes left to right — each pipe receives the previous pipe's output:
{{ interpolated_value | pipe_name_1 | pipe_name_2 |... | pipe_name_n }}Add uppercase after date:
{{ now | date:'medium' | uppercase}} // JUN 24, 2020, 5:00:00 PMThe author was in Singapore (UTC+8) when writing the original article, so times may show as 5 PM. Readers in UTC+7 may see 4 PM depending on locale settings.
Built-in pipes
Import CommonModule from @angular/common to use these. Commonly used built-ins:
| Pipe | Description |
|---|---|
DatePipe | Formats a date |
UpperCasePipe | Converts text to uppercase |
LowerCasePipe | Converts text to lowercase |
CurrencyPipe | Displays a currency value |
DecimalPipe | Displays a decimal number |
PercentPipe | Displays a percentage |
JsonPipe | Displays JSON |
AsyncPipe | Subscribes to an observable and unsubscribes when the view is destroyed |
See the full list in CommonModule.
Writing a custom pipe
A typical CRUD app reuses the same form HTML for add and edit. When editing, the route includes an itemId; when adding, it doesn't. Without a pipe, every template repeats:
{{ itemId ? "Edit" : "Add" }}A typo (Adđ instead of Add, with Vietnamese input method enabled) motivated a small reusable pipe.
Step 1: Implement PipeTransform
interface PipeTransform { transform(value: any, ...args: any[]): any;}Example implementation:
export class AppTitlePipe implements PipeTransform { transform(resourceId: string): string { return resourceId ? 'Edit' : 'Add'; }}Truthy resourceId returns Edit; otherwise Add.
Step 2: Add the @Pipe decorator
@Pipe({ name: 'appTitle',})export class AppTitlePipe implements PipeTransform { transform(resourceId: string): string { return resourceId ? 'Edit' : 'Add'; }}The name property is required — here, appTitle. Add AppTitlePipe to the module's declarations array where you use it.
<h2 class="ibox-title">{{ userId | appTitle }} User</h2>Naming conventions (Angular Style Guide):
- Class:
UpperCamelCase(e.g.AppTitlePipe) - Pipe
name:camelCase(e.g.appTitle) — no hyphens
Custom pipe parameters
Some pages need Set / Change instead of Add / Edit:
transform( resourceId: string, addText: string = "Add", editText: string = "Edit"): string { return resourceId ? editText : addText;}{{ userId | appTitle:"Set":"Change"}}- First
transformargument: the piped value (userId) - Additional template parameters map to arguments 2, 3, … in order
Change detection and pipes
Primitive types
With string resourceId, when the value changes, the pipe re-runs and the UI updates:
export class PipeExampleComponent implements OnInit { userIdChangeAfterFiveSeconds = '14324'; time$: Observable<number> = timer(0, 1000).pipe( map((val) => 5 - (val + 1)), startWith(5), finalize(() => { this.userIdChangeAfterFiveSeconds = ''; }), takeWhile((val) => val >= 0) );}<p> Set userId to empty string after {{ timer | async }} seconds, notice the text "Edit" will be set to "Add"</p><pre ngNonBindable>{{ userIdChangeAfterFiveSeconds | appTitle}}</pre><div>Form title: {{ userIdChangeAfterFiveSeconds | appTitle}} User</div>
Primitives (string, boolean, number) trigger pipe updates straightforwardly.
Reference types
Given a users array:
users: User[] = [ { name: "Tiep Phan", age: 30 }, { name: "Trung Vo", age: 28 }, { name: "Chau Tran", age: 29 }, { name: "Tuan Anh", age: 16 }];An isAdult pipe filters users over 18:
@Pipe({ name: 'isAdult',})export class IsAdultPipe implements PipeTransform { transform(arr: User[]): User[] { return arr.filter((x) => x.age > 18); }}<div class="row"> <div class="col-xs-6"> <h4>Full user list</h4> <div *ngFor="let user of users">{{ user.name }}</div> </div> <div class="col-xs-6"> <div class="ml-4"> <h4>Adult user list</h4> <div *ngFor="let user of users | isAdult">{{ user.name }}</div> </div> </div></div>
Tuan Anh (age 16) is correctly excluded from the adult list.
Add a user with a form — pushing into the array doesn't update the piped list:

addUser() { this.users.push(this.newUser); this.newUser = new User()}By default, pipes are pure — Angular runs them only on a pure change to the input: a new primitive value, or a new object reference for objects/arrays/functions.
Mutating an array in place doesn't change its reference, so the pipe doesn't re-execute.
Reference checks are much faster than deep equality checks, so prefer pure pipes when possible.
Two fixes:
1. Update the variable reference
addUserByUpdateReference() { this.users = [...this.users, this.newUser]; this.newUser = new User();}
2. Set pure: false (impure pipe)
@Pipe({ name: 'isAdult', pure: false})Use impure pipes carefully. Deep change detection on large collections hurts performance noticeably.
Summary
We've covered what pipes are, built-in and custom pipes, parameters and chaining, and the difference between pure and impure pipes in change detection.
Further reading:
- https://angular.io/guide/pipes
- https://angular.io/api/common/CommonModule#pipes
- Angular pipe singular/plural
Youtube Video
Code sample
https://stackblitz.com/edit/angular-100-days-of-code-day-18-pipes
