Pipes

Chapter 7
Pipes — Built-in and Custom
Transforming a value for display, right inside the template, without changing the underlying data

In React, formatting a value for display means calling a function in the JSX — {formatPrice(amount)}, {date.toLocaleDateString()}. Angular has a dedicated template feature for this: a pipe, applied with the | symbol, transforming a value purely for display while leaving the original data untouched. Several common transformations ship built in, and writing custom ones is straightforward.

Built-in Pipes

<p>{{ name | uppercase }}</p> <!-- PHILIP --> <p>{{ price | currency:'EUR' }}</p> <!-- €19.99 --> <p>{{ today | date:'fullDate' }}</p> <!-- Tuesday, June 23, 2026 --> <p>{{ ratio | percent }}</p> <!-- 42% --> <p>{{ data | json }}</p> <!-- debug: prints the object as JSON -->

A pipe takes the value on its left and transforms it for display. Arguments come after a colon — currency:'EUR' sets the currency code, date:'fullDate' picks a format. The original name, price, and today properties are never modified; only what appears on screen is transformed. The json pipe is especially handy while learning — it's the quickest way to dump an object's contents into the template to see its shape.

Chaining Pipes

<p>{{ name | uppercase | slice:0:3 }}</p> <!-- "PHILIP" -> "PHI" -->

Multiple pipes can be chained with successive | symbols, each receiving the output of the one before it — applied left to right, the same direction as reading. Here uppercase runs first, then slice takes the first three characters of the result.

Most built-in pipes need importing
Pipes like CurrencyPipe, DatePipe, UpperCasePipe, and JsonPipe live in @angular/common and must be added to the component's imports array (individually, or via CommonModule) before use — the same "import what the template uses" rule as ngClass from Chapter 4. Forgetting produces the familiar "unknown pipe" template error.

Writing a Custom Pipe

// truncate.pipe.ts import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'truncate', standalone: true }) export class TruncatePipe implements PipeTransform { transform(value: string, limit = 20): string { if (value.length <= limit) return value; return value.slice(0, limit) + '…'; } }
<!-- usage, after importing TruncatePipe into the component --> <p>{{ longText | truncate:50 }}</p>

A custom pipe is a class implementing PipeTransform — a single transform(value, ...args) method returning the transformed result. The @Pipe decorator's name is what's used in the template (| truncate), and any extra parameters after the value become the pipe's arguments (truncate:50 passes 50 as limit). It's a standalone pipe, imported into a component's imports array exactly like a standalone component.

A pipe is the template-friendly version of a custom hook's spirit
A custom pipe and a React formatting function solve the same problem — reusable display transformation. Angular's version is declarative and lives in the template (| truncate:50 reads cleanly inline), and being a class registered with the framework, the same pipe is trivially reusable across every component that imports it, without re-importing a utility function in each file.

The async Pipe — A Preview

<p>{{ user$ | async | json }}</p>

One built-in pipe deserves a special mention now: async. It subscribes to an Observable (or Promise) and renders its latest emitted value, automatically — directly addressing the async-state limitation flagged in Chapter 6's warning. It comes into its own with the HTTP client and RxJS in Chapter 11, but it's worth recognizing here as a pipe, since that's exactly what it is.

Angular pipeReact equivalent
{{ x | uppercase }}{x.toUpperCase()}
{{ x | currency:'EUR' }}{formatCurrency(x)}
{{ x | date:'fullDate' }}{x.toLocaleDateString(...)}
{{ x | myCustomPipe }}{myFormatFunction(x)}

Coding Challenges

Challenge 1

Build a component displaying a name (uppercase pipe), a price (currency pipe), and today's date (date pipe with a readable format), remembering to import the needed pipes.

📄 View solution
Challenge 2

Write a custom TruncatePipe (with a configurable limit argument defaulting to 20, appending an ellipsis when it truncates) and use it on a long string with an explicit limit.

📄 View solution
Challenge 3

Write a custom FileSizePipe converting a number of bytes into a human-readable string (e.g. 1536 -> "1.5 KB", 2097152 -> "2 MB"), and use it on a few different byte values.

📄 View solution

Chapter 7 Quick Reference

  • A pipe{{ value | pipeName }} — transforms a value for display, leaving the data unchanged
  • Arguments follow a colon: currency:'EUR', date:'fullDate', slice:0:3
  • Chain pipes with successive | symbols, applied left to right
  • Built-in pipes (currency/date/uppercase/json…) live in @angular/common and must be imported
  • Custom pipe — a class with @Pipe({ name }) implementing transform(value, ...args)
  • async pipe — subscribes to an Observable/Promise and renders its latest value (Chapter 11)
  • Next chapter: template-driven forms