Pipes
Understand how Angular pipes transform values for display, how built-in and custom pipes work, and when to use pure, impure, and async pipes.
Lesson content
What is an Angular Pipe? An Angular pipe transforms a value inside a template without changing the original source data. Pipes are useful for formatting text, dates, numbers, currencies, percentages, and asynchronous values. Simple Definition Pipe = Input Value + Transformation + Display Value Basic Syntax {{ value | pipeName }} Example userName = 'ajay rajpoot'; <p> {{ userName | uppercase }} </p> Output AJAY RAJPOOT Pipe Flow Component Value ↓ Pipe Receives Value ↓ transform() Runs ↓ Formatted Value ↓ Template Displays Result Why Use Pipes? Keep formatting logic outside templates Reuse transformations across components Format dates, numbers, and currencies Transform text for display Display Observable and Promise values Keep source data unchanged Importing Built-In Pipes Standalone components import the pipes used by their templates. import { CurrencyPipe, DatePipe, DecimalPipe, LowerCasePipe, PercentPipe, TitleCasePipe, UpperCasePipe } from '@angular/common'; import { Component } from '@angular/core'; @Component({ selector: 'app-profile', standalone: true, imports: [ CurrencyPipe, DatePipe, DecimalPipe, LowerCasePipe, PercentPipe, TitleCasePipe, UpperCasePipe ], template: ` <p>{{ name | titlecase }}</p> <p>{{ salary | currency:'USD' }}</p> ` }) export class ProfileComponent { name = 'ajay singh rajpoot'; salary = 5000; } Common Built-In Pipes Pipe Purpose uppercase Converts text to uppercase lowercase Converts text to lowercase titlecase Capitalizes words date Formats date values currency Formats monetary values number Formats decimal numbers percent Formats a value as a percentage json Displays values as JSON for debugging slice Returns part of a string or array keyvalue Converts an object or map into key-value entries async Reads Observable or Promise values 1. UpperCasePipe message = 'hello angular'; {{ message | uppercase }} Output HELLO ANGULAR 2. LowerCasePipe message = 'HELLO ANGULAR'; {{ message | lowercase }} Output hello angular 3. TitleCasePipe fullName = 'ajay singh rajpoot'; {{ fullName | titlecase }} Output Ajay Singh Rajpoot 4. DatePipe The date pipe formats Date objects, timestamps, and supported date strings. today = new Date(); {{ today | date }} Common Date Formats {{ today | date:'short' }} {{ today | date:'medium' }} {{ today | date:'long' }} {{ today | date:'full' }} {{ today | date:'shortDate' }} {{ today | date:'mediumDate' }} {{ today | date:'longDate' }} {{ today | date:'fullDate' }} {{ today | date:'yyyy-MM-dd' }} {{ today | date:'dd MMM yyyy' }} {{ today | date:'hh:mm a' }} DatePipe Parameters {{ value | date:format:timezone:locale }} Example with Timezone {{ today | date:'medium':'UTC' }} 5. CurrencyPipe The currency pipe formats a number as money. price = 1299.5; {{ price | currency:'USD' }} Output $1,299.50 CurrencyPipe Syntax {{ value | currency:currencyCode:display:digitsInfo:locale }} Examples {{ price | currency:'USD' }} {{ price | currency:'USD':'code' }} {{ price | currency:'USD':'symbol' }} {{ price | currency:'USD':'symbol':'1.0-0' }} 6. DecimalPipe The number pipe formats decimal numbers. rating = 4.5678; {{ rating | number:'1.1-2' }} Output 4.57 digitsInfo Format minimumIntegerDigits . minimumFractionDigits - maximumFractionDigits 1.2-3 This means: At least one integer digit At least two fraction digits At most three fraction digits 7. PercentPipe progress = 0.756; {{ progress | percent }} Output 76% With Decimal Places {{ progress | percent:'1.1-2' }} 8. JsonPipe The json pipe converts a value into formatted JSON. user = { id: 1, name: 'Ajay', active: true }; <pre> {{ user | json }} </pre> Use it mainly for debugging, not as the final user interface. 9. SlicePipe The slice pipe returns part of a string or array. String Example message = 'Angular Learning'; {{ message | slice:0:7 }} Output Angular Array Example products = [ 'Laptop', 'Keyboard', 'Mouse', 'Monitor' ]; @for ( product of products | slice:0:2; track product ) { <p>{{ product }}</p> } 10. KeyValuePipe The keyvalue pipe converts an object or map into an iterable list of key-value pairs. user = { name: 'Ajay', role: 'Developer', active: true }; @for ( item of user | keyvalue; track item.key ) { <p> {{ item.key }}: {{ item.value }} </p> } 11. AsyncPipe The async pipe subscribes to an Observable or Promise and returns its latest value. It also manages subscription cleanup when the view is destroyed or the source reference changes. Observable Example import { AsyncPipe } from '@angular/common'; import { Component } from '@angular/core'; import { interval, map } from 'rxjs'; @Component({ selector: 'app-clock', standalone: true, imports: [ AsyncPipe ], template: ` <p> {{ clock$ | async }} </p> ` }) export class ClockComponent { clock$ = interval(1000).pipe( map(() => new Date() .toLocaleTimeString() ) ); } Using AsyncPipe with Control Flow @if (users$ | async; as users) { @for ( user of users; track user.id ) { <p> {{ user.name }} </p> } } @else { <p>Loading users...</p> } AsyncPipe Flow Observable ↓ AsyncPipe Subscribes ↓ Latest Value Received ↓ Template Updates ↓ View Destroyed ↓ AsyncPipe Unsubscribes Passing Parameters to Pipes Pipe parameters are separated using colons. {{ value | pipeName:parameter1:parameter2 }} Examples {{ today | date:'dd MMM yyyy' }} {{ price | currency:'USD':'symbol':'1.2-2' }} {{ rating | number:'1.1-2' }} {{ message | slice:0:20 }} Chaining Pipes Multiple pipes can be applied from left to right. {{ value | pipeOne | pipeTwo }} Example today = new Date(); {{ today | date:'fullDate' | uppercase }} Flow Date Value ↓ date Pipe ↓ Formatted Date String ↓ uppercase Pipe ↓ Uppercase Date Pipe Operator Precedence The pipe operator has lower precedence than arithmetic, logical, and nullish-coalescing operators. {{ price * quantity | currency:'USD' }} The multiplication runs before the currency pipe. Use parentheses when the intended expression may be unclear. {{ (discountedPrice ?? originalPrice) | currency:'USD' }} Create a Custom Pipe Use Angular CLI to generate a pipe. ng generate pipe shared/initials Short form: ng g p shared/initials Custom Initials Pipe import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'initials', standalone: true }) export class InitialsPipe implements PipeTransform { transform( value: string ): string { if (!value.trim()) { return ''; } return value .trim() .split(/\s+/) .map( word => word.charAt(0) ) .join('') .toUpperCase(); } } Import the Custom Pipe @Component({ selector: 'app-profile', standalone: true, imports: [ InitialsPipe ], template: ` <p> {{ fullName | initials }} </p> ` }) export class ProfileComponent { fullName = 'Ajay Singh Rajpoot'; } Output ASR PipeTransform Interface A custom pipe should implement PipeTransform . export interface PipeTransform { transform( value: unknown, ...args: unknown[] ): unknown; } The first argument is the input value. Remaining arguments represent pipe parameters. Custom Pipe with Parameters Truncate Pipe import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'truncate', standalone: true }) export class TruncatePipe implements PipeTransform { transform( value: string, maximumLength = 30, suffix = '...' ): string { if ( !value || value.length <= maximumLength ) { return value; } return value.slice( 0, maximumLength ) + suffix; } } Usage {{ description | truncate:50:'…' }} Pure Pipes Angular pipes are pure by default. A pure pipe runs when: The primitive input value changes An object reference changes An array reference changes A pipe parameter changes @Pipe({ name: 'activeUsers', standalone: true, pure: true }) export class ActiveUsersPipe implements PipeTransform { transform( users: User[] ): User[] { return users.filter( user => user.active ); } } Pure Pipe Flow Input Reference ↓ Did Reference Change? ┌──────┴──────┐ │ │ Yes No │ │ ▼ ▼ Run Pipe Reuse Result Object and Array Mutation A pure pipe may not run when an existing array or object is mutated without changing its reference. Avoid this.users.push(newUser); Prefer this.users = [ ...this.users, newUser ]; The second example creates a new array reference, allowing pure-pipe change detection to identify the update. Impure Pipes An impure pipe runs frequently during change detection. @Pipe({ name: 'dynamicFilter', standalone: true, pure: false }) export class DynamicFilterPipe implements PipeTransform { transform( users: User[], searchTerm: string ): User[] { const normalizedTerm = searchTerm .trim() .toLowerCase(); return users.filter( user => user.name .toLowerCase() .includes( normalizedTerm ) ); } } Warning Impure pipes can run many times and may create performance problems, especially when they process large arrays or perform expensive operations. Prefer signals, computed values, component logic, or RxJS transformations for frequently changing filtered data. Pure vs Impure Pipes Pure Pipe Impure Pipe Default behavior Uses pure: false Runs when inputs or references change Runs frequently during change detection Better performance Potentially expensive Should be deterministic May depend on mutable external state Recommended for most transformations Use only when genuinely necessary Pipes with Signals Signals and pipes can work together in templates. import { Component, signal } from '@angular/core'; import { CurrencyPipe } from '@angular/common'; @Component({ selector: 'app-price', standalone: true, imports: [ CurrencyPipe ], template: ` <p> {{ price() | currency:'USD' }} </p> ` }) export class PriceComponent { price = signal(1299.5); } When the signal changes, Angular updates the template and the pipe receives the new value. Pipe vs Computed Signal Pipe Computed Signal Transforms values for templates Creates derived reactive state Reusable across templates Usually belongs to component or service state Called with pipe syntax Read by calling the signal name | titlecase displayName() Best for presentation formatting Best for application-derived values Computed Signal Example firstName = signal('Ajay'); lastName = signal('Rajpoot'); fullName = computed(() => `${this.firstName()} ${this.lastName()}` ); Use a computed signal when the derived value is part of component state or is reused by TypeScript logic. Pipe vs Component Method Pipe Component Method Reusable transformation Component-specific behavior Pure pipes can reuse results Method may run during template checking Good for display formatting Good for actions and simple component logic Avoid Expensive Template Methods Avoid: {{ calculateLargeReport() }} Prefer: {{ report | reportSummary }} A computed signal can also be appropriate when the result depends on reactive component state. Transformation Logic Outside Templates Pipes are designed primarily for template transformations. When the same logic is needed in services, utilities, or other TypeScript code, extract it into a reusable function. Reusable Function export function toInitials( value: string ): string { return value .trim() .split(/\s+/) .map( word => word.charAt(0) ) .join('') .toUpperCase(); } Pipe Uses the Function @Pipe({ name: 'initials', standalone: true }) export class InitialsPipe implements PipeTransform { transform( value: string ): string { return toInitials(value); } } Service Uses the Same Function @Injectable({ providedIn: 'root' }) export class UserService { createInitials( name: string ): string { return toInitials(name); } } Avoid injecting a pipe into a service only to call its transform() method. Locale-Aware Formatting Outside Templates For formatting outside templates, Angular provides standalone formatting functions. Pipe Formatting Function DatePipe formatDate() CurrencyPipe formatCurrency() DecimalPipe formatNumber() PercentPipe formatPercent() Example import { formatNumber } from '@angular/common'; import { inject, LOCALE_ID } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class NumberFormatterService { private locale = inject(LOCALE_ID); format(value: number) { return formatNumber( value, this.locale, '1.2-2' ); } } Null and Undefined Values Custom pipes should safely handle empty values when those values are possible. @Pipe({ name: 'safeTitle', standalone: true }) export class SafeTitlePipe implements PipeTransform { transform( value: string | null | undefined ): string { return value ? value .trim() .toUpperCase() : ''; } } Testing a Custom Pipe describe( 'InitialsPipe', () => { const pipe = new InitialsPipe(); it( 'returns initials', () => { expect( pipe.transform( 'Ajay Singh Rajpoot' ) ).toBe('ASR'); } ); it( 'returns empty text', () => { expect( pipe.transform('') ).toBe(''); } ); } ); Pure custom pipes are usually easy to test because they behave like normal transformation functions. Common Mistakes 1. Mutating Data Inside a Pipe Avoid: transform(users: User[]) { users.sort(); return users; } Prefer: transform(users: User[]) { return [...users].sort( (first, second) => first.name.localeCompare( second.name ) ); } 2. Making HTTP Requests Inside a Pipe Do not use pipes for HTTP requests or other business operations. Pipe = Display Transformation Service = Data Access and Business Logic 3. Creating Expensive Impure Pipes Avoid filtering or sorting large collections repeatedly with an impure pipe. 4. Forgetting to Import a Pipe A standalone component must import every standalone pipe used in its template. 5. Using Pipes for Application State Use signals, computed values, services, or state-management tools for application state. 6. Injecting Pipes into Services Extract reusable transformation logic into a normal function instead. 7. Using json Pipe in Production UI The json pipe is helpful for debugging but usually unsuitable for user-facing interfaces. Best Practices Use pipes for display transformations Keep custom pipes pure and deterministic Implement PipeTransform Use lower camel case for pipe names Use PascalCase with the Pipe suffix for class names Do not mutate arrays or objects inside pipes Avoid HTTP requests and business logic inside pipes Prefer immutable data updates with pure pipes Use the async pipe for template Observables and Promises Use computed signals for component-derived state Extract shared non-template logic into reusable functions Avoid impure pipes unless no cleaner design exists Interview Questions What is an Angular pipe? An Angular pipe declaratively transforms a value for display inside a template. How do you use a pipe? {{ value | pipeName }} What is a custom pipe? A custom pipe is a TypeScript class decorated with @Pipe() that provides a transform() method. What is the difference between a pure and impure pipe? A pure pipe runs when its input value, parameters, or object references change. An impure pipe runs frequently during change detection. Are Angular pipes pure by default? Yes. Angular pipes are pure by default. What does AsyncPipe do? AsyncPipe subscribes to an Observable or Promise, displays its latest value, updates the template, and manages subscription cleanup. Why might a pure pipe not detect an array change? A pure pipe checks the array reference. Mutating the existing array does not create a new reference. Can pipes make HTTP requests? They technically can call other code, but pipes should not perform HTTP requests or business operations. Use a service for data access. Should a pipe be injected into a service? No. Extract the shared transformation into a standalone function and use that function from both the pipe and the service. What is pipe chaining? Pipe chaining passes the output of one pipe into the next pipe. {{ today | date:'fullDate' | uppercase }} Key Point A pipe transforms a value for presentation while leaving the original component data unchanged. Easy Formula Built-In Pipe = Ready-Made Transformation Custom Pipe = @Pipe + PipeTransform + transform() Pure Pipe = Runs When Input Changes Impure Pipe = Runs Frequently AsyncPipe = Subscribe + Display + Cleanup Source Value ↓ Pipe ↓ Parameters ↓ transform() ↓ Displayed Value