Pipes
Angular Pipes
Table of Contents
- What are pipes
- Built-in pipes
- custom pipes
- Pure vs Impure pipes
- Async pipe
- Pipe chaining
- Parameterized pipes
- Best practices
- Interview question
What are Pipes?
Pipes are simple function that accept an input value and return a transformed output value. They are used in template expressions using the pipe operator (|) to transform data for display.
Key Characteristics:
- Transform data in templates without changing the original data
- Can be chained together
- Can accept paramenter
- Can be pure or impure
- Resuable accross the application
Basic Syntax:
{{ value || pipeName }}
{{value || pipeName: paramenter1: parameter2 }}
Built-in Pipes
Angular provide built-in pipes for common transformations:
1. DatePipe
Formates data values according to locale rules.
// component
export class AppComponent {
today = new Date();
}
// template
{{today | date }} // default format
{{today | date:'short' }} // 1/1/24, 12:00 AM
{{today | date: 'medium' }} // Jan 1, 2024, 12:00:00 AM
{{today | date: 'long' }} // January 1, 2024 at 12:00:00 AM GMT+0
{{today | date:'fullDate' }} // Monday, January 1, 2024
{{today | date: 'shortTime' }} //12:00 AM
{{today | date: 'dd/mm/yyyy' }} // 01/01/2024
{{today | date: 'dd mmm yyyy HH:mm:ss' }} // 01 Jan 2024 00:00:00
2. CurrencyPipe
Transform numbers to currency strings.
//template
{{1234.56 | currency }} //$1234.56 (default USD)
{{1234.56 | currency:'EUR' }} // €1234.56
{{1234.56 | currency:'INR' }} // ₹1234.56
{{1234.56 | currency:'GBP':'code' }} // GBP1234.56
{{1234.56 | currency:'USD': 'symbol':'1.0-0' }} //$1235 (no decimal)
3. DecimalPipe
Formats numbers with decimal points.
//template
{{3.14159 | number }} //3.142 (default)
{{3.14159 | number:'1.0-0' }} //3 (no decimal)
{{3.14159 | number:'3.1-5' }} //003.14159
{{1234567.89 | number:'1.2-2' }} // 1234567.89
4. PercentPipe
Transform numbers to percentage strings.
//tamplate
{{0.25 |percent }} //25%
{{0.5 |percent:'1.2-2' }} //50.00%
{{1.5 |percent }} //150%
5. UpperCasePipe / LowerCasePipe / TitleCasePipe
Transform text case.
//tamplate
{{'hellow world' | uppercase }} // HELLOW WORLD
{{'hellow world' | lowercase }} // hellow world
{{'hellow world' | titlecase }} // Hellow World
6. SlicePipe
creates a subset of array or strings.
// component
export class AppComponent {
items =['Apple', 'Banana','Cherry', 'Date', 'Elderberry'];
text = 'Hellow World';
}
// Template
{{item | slice: 1:3 }} //Banana, Cherry
{{text | slice: 0:5}} // Hello
7. JsonPipe
Converts object to JSON strings (useful for debugging).
//Component
export class AppComponent {
user = {name: 'John', age: 30, city: 'New York'};
}
// Template
<pre>{{user | json }}</pre>
// Output : {"name": "John", "age": 30, "city": "New York"}
8. KeyValuePipe
Transforms Objects or Maps into an array of key-value pairs.
//Component
export class AppComponent {
object = {name: 'John', age: 30, city: 'New York'};
}
//Template
<div *ngFor="let item of object | keyvalue">
{{item.key}}: {{item:value }}
</div>
Custom Pipes
Creating a Custom Pipe
Step 1: Generate pipe
ng generate pipe pipes/custom-pipe-name
# or
ng g p pipes/custom-pipe-name
Step 2: Implement pipe logic
Example 1: Reverse String Pipe
import { pipe, PipeTransform } from '@angular/core';
@pipe({
name: 'reverse',
standalsone: true
})
export class ReversePipe implements PipeTransform {
transform(value: string): string {
if(!value) return value;
return value.split('').reverse().join('');
}
}
Usage:
{{'Angular' | reverse }} <!-- Output: ralugnA -->
Example 2: Word count pipe
import { pipe, PipeTransform } from '@angular/core';
@pipe({
name: 'wordCount',
standalsone: true
})
export class WordCountPipe implements PipeTransfrom {
transfrom(value: string): number {
if(!value) return value;
return value.trim().split(/\s+/).length;
}
}
Usage:
{{'Hellow world from Angular' | wordCount }} <!-- Output: 4 -->
Example 3: Filter Pipe (with Parameters)
import { pipe, PipeTransform } from '@angular/core';
@pipe({
name: 'filter',
standalsone: true
})
export class FilterPipe implements PipeTransfrom {
transform(items: any[], searchText: string, field?: string): any[] {
if(!items || !searchText) return items;
searchText = searchText.toLowerCase();
return items.filter( item => {
if(field){
return item[field]?.toLowerCase().includes(searchText);
}
return JSON.stringify(item).toLowerCase()includes(searchText);
});
}
}
Usase:
<input [(ngModel)]="searchTerm" placeholder="search...">
<div *ngFor="let user of users | filter: searchTerm:'name'">
{{user.name}}
</div>
Example 4: Truncate Pipe
import { pipe, PipeTransform } from '@angular/core';
@pipe({
name: 'truncate',
standalsone: true
})
export class TruncatePipe implements PipeTransform {
transfrom(value: string, limit: number = 50, ellipsis: string ='...') : string {
if(!value) return value;
if(value.length<= limit){
return value;
}
return value.substring(0, limit) + ellipsis;
}
}
Usage:
{{longText | truncate: 100:'...'}}
Exaple:5 Time Ago Pipe
import { pipe, PipeTransform } from '@angular/core';
@pipe({
name: 'timeAgo',
standalsone: true
})
export class TimeAgoPipe implements PipeTransform {
transform(value: Date | string): string {
if(!value) return '';
const date = value instanceof Date? value: new Date(value);
const now = new Date();
const seconds = Math.floor((now.getTime() - date.getTime())/1000);
const intervals = {
year: 31536000,
month: 2592000,
week: 604800,
day: 86400,
hour: 3600,
minute: 60,
second: 1
};
for( const [name, secondsInInterval] of Object.entries(intervals)) {
const interval = math.floor(seconds/secondsInInterval);
if(interval >=1){
return interval===1 ?`${interval} ${name} ago`
: `${interval} ${name}s ago`;
}
}
return 'just now';
}
}
Usage:
{{post.createAt | timeago }} <!-- Output: 2 hours ago -->
Pure VS Impure Pipes
Pure Pipes (Default)
Characteristics:
- Called only when Angular detects a pure change to the input value
- Pure changes: primitive values (String, Number, Boolean) or object refrences
- More performant as they're called less frequently
- Default behavior (pure: true)
@pipe({
name: 'purePipe',
pure: true // Default
})
export class PurePipe implements PipeTransform {
transform(value: any): any {
console.log('Pure pipe called');
return value;
}
}
Example:
// component
export class AppComponent {
items = ['Apple', 'Banana', 'Cherry'];
addItem() {
// This WON'T trigger pure pipe (same reference)
this.items.push('Date');
// This will trigger pure pipe (new reference)
this.items = [...this.items, 'Date'];
}
}
Impure Pipe
Characteristics:
- Called on every change detection cycle
- Detects changes within objects or arrays
- Less performant but more responsive to data changes
- Must explicitly set pure: false
@pipe({
name: 'impureFilter',
pure: false // Impure pipe
})
export class ImpureFilterPipe implements PipeTransform {
transform(items: any[], searchText: string): any[]{
console.log('Impure pipe called');
if(!items || !searchText){
return items;
}
return items.filter(item => item.toLowerCase().includes(searchText.toLowerCase())
);
}
}
When to use Impure Pipes:
- Filtering/sorting dynamic arrays
- Real-time data transformations
- When you need to detect changes within object/arrays
Performance Consideration:
// ❌ Bad: Impure pipe for simple transformation
@pipe({ name: 'badPipe', pure: false})
//✅ Good: Pure pipe with immutable data
@pipe({ name: 'goodPipe', pure: true})
// Update data immutably: this.items = [...this.items, newItem]
Async Pipe
The Async Pipe is a spacial built-in pipe that subscribes to observables or Promises and returns the latest value.
Key Benfits:
- Atomatic Subscription Managment: Automatically subscribes and Unsbscribes
- Memory leak Prevention: Prevents memory leaks
- Cleaner code: No need for manual subscription in component
Observable Example:
//Component
import { Component } from '@angular/core';
import { Observable, interval, map } from 'rxjs';
@Component({
selector: 'app-async-demo',
template: `
<h3?>Current Time: {{time$ | async | date: 'medium' }}</h3>
<h3?>User Name: {{user$ | async | json }}</h3>
`
})
export class AsyncDemoComponent {
// Observable that emits every second
time$: Observable<Date> = interval(1000).pipe(
map(() => new Date())
);
// Observable from HTTP call
user$: Observable<user> = this.http.get<User>('/api/User');
constructor(private http: HttpClient){}
}
Promise Example:
// component
export class AppComponent {
dataPromise: Promise<string>;
constructor(){
this.dataPromise = this.fatchData();
}
fatchData(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => resolve('Data loaded!'), 2000);
});
}
}
// Template
<div>{{dataPromise | async }}</div>
Multiple async Pipes (Same Observable):
//❌ Bad: Multiple subscriptions
<div>{{user$ | async }}</div>
<div>{{user$ | async }}</div>
<div>{{user$ | async }}</div>
//✅ Good Using as Keyword (Angular 13+)
<div *ngIf="user$ | async as user">
<h3>{{user.name}}</h3>
<p> {{user.email}}</p>
<p> {{user.age}}</p>
</div>
//✅ Good Using ng-container
<ng-container *ngIf=="user$ | async as user">
<h3>{{user.name}}</h3>
<p> {{user.email}}</p>
</ng-container>
Error handling with async pipe
import { catchError, of } from 'rxjs';
export class AppComponent {
data$ = this.http.get('/api/data').pipe(
catchError(error => {
console.error('Error:', error);
return of(null); // Return default value
})
);
}
// Template with error handeling
<div *ngIf="data$ | async as data; else loading">
{{data | json }}
</div>
<ng-tamplate #loading>Loading....</ng-template>
Pipe Chaining
Pipe can be chained together, with the output of one pipe becoming input of the next.
// component
export class AppComponent {
birthday = new Date(1990, 5, 15);
price = 1234.56;
message= 'hellow world from Angular';
}
// Tamplate
{{birthday | date:'fullDate' | uppercase}}
//Output: FRIDAY, JUNE 15, 1990
{{price | currency:'USD' | lowercase }}
//Output: $1,234.56
{{message | slice:0:11 | titlecase }}
//Output: Hellow World
{{user$ | async | json }}
// First resolve observable, then convert to json
Complex Chaining Example:
// Component
export class AppComponent {
product$ = this.http.get<Product[]>('/api/products');
}
// Tamplate
<div *ngFor="let product for products$ | async | slice:0:5">
<h3> {{product.name | titlecase}} </h3>
<p> {{product.discription | truncate:100 }}</p>
<p> {{product.price | currency:'USD' }}</p>
<p> {{product.createAt | date:'short' | uppercase }} </p>
</div>
Parameterized Pipes
Pipes can accept parameters to customize their behavior.
Single Paramenter:
{{ value | pipeName: parameter }}
{{'hello' | truncate:5}}
Multiple Parameters:
{{value | pipeName:param1:param2:param3 }}
{{today | date: 'dd/mm/yyyy': 'UTC':'+0530'}}
Custom Pipe with Multiple Paramenters:
import { Pipe, PipeTransform } from '@angular/core';
@pipe({
name: 'highlight',
standalone: true
})
export class HighlightPipe implements PipeTransform {
transform(
value: string,
searchTerm: string,
backGroundColor: string = 'yellow',
textColor: string ='black'
): string {
if(!value ||!searchTerm) return value;
const regex = new RegExp(searchTerm, 'gi');
return value.replace( regex, match =>
`<span style="backgroun-color: ${backgroundColor}; color: ${textColor}">
${match}
</span>`
);
}
}
usage:
<div [innerHtml]="text | highlight: 'Angular':'yellow':'red'"></div>
Bast Practices
1. Keep Pipe pure when possible
//✅ Good: Pure pipe with immutable data
@pipe({name: 'filter', pure: true});
// Update data immutable
this.items = [...this.items, newItem];
2. Avoid Complex logic in Pipes
//❌ Bad: Complex business logic
@pipe({ name: 'complexCalculation' })
export class ComplexPipe implements PipeTransform {
trasfrom(value: number): number {
// Multiple API calls, complex calculations
return complexResult;
}
}
// ✅ Good: Simple transfromation
@pipe({ name: 'formatCurrency' })
export class FormateCurrencyPipe implements PipeTransform {
transform( value: number, currency: string='USD'): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency
}).fromate(value);
}
}
3. USe Async Pipe for Observables
//❌ Bad: Manual subscription
export class AppComponent implements OnInit, OnDestroy {
data: any;
subscription: Subscription;
ngOnInit(){
this.subscription = this.service.getData().subscription(
data => this.data = data
);
}
ngDestroy(){
this.subscription.unsubscribe();
}
}
//✅ Good : Async pipe
export class AppComponent {
data$ = this.service.getData();
}
// Tamplate: {{data$ | async }}
4. Minimize Async Pipe Duplication
//❌ Bad: Multiple subscriptions
<div> {{user$ | async }}</div>
<div> {{user$ | async }}</div>
//✅ Good: Single subscription with alias
<ng-container *ngIf="user$ | async as user">
<h3>{{user.name}}</h3>
<p> {{user.email}}</p>
<p> {{user.age}}</p>
</ng-container>
5. USe Descriptive Pipe Names
//❌ Bad
@pipe({name: 'p1'})
//✅ Good
@pipe({name: 'formatephoneNumber'})
6. Handle Null/Undefined Values
@Pipe({name: 'safePipe'})
export class SafePipe implements PipeTransform {
transform( value: any): any {
if(value === null || value === undefined){
return ''; // or default value
}
return value;
}
}
7. **Consider Performance for Impure Pipes **
// USe impure pipe sparingly
@pipe({
name: 'expensiveFilter',
pure: false //Only when necessary
})
export class ExpensiveFilterPipe implements PipeTransform {
transform(items: any[], filter: string): any[]{
// This will run on every change detection
return items.filter(item => item.includes(filter));
}
}
Interview Question & Answer
Basic level Questions
Q1: What are pipes in Angular?
Answer: Pipes are simple functions used in template expressions to transform data for display. They take an input value and return a transformed output without changing the original data. Pipes are denoted by the pipe operator (|) in templates.
Example:
{{'hello' | uppercase }} //Output: HELLO
{{price | currency: 'USD'}} // Output: $99.99
Q2. Name some built-in pipes in Angular.
Angular Angular provides sevral built-in pipes:
- DatePipe: Formates dates
- CurrencyPipe: Formates currency value
- DecimalPipe: fprmates number with decimals
- PercentagePipe: formates percentage
- UpperCasePipe/LowerCasePipe/TitleCasePipe: Taxt case transformation
- SlicePipe: Extracts subset of arrays or string
- JSONPipe: Converts object to JSON strings
- AsyncPipe: Handle observable & Promises
- KeyValuePipe: Transforms objects to key-value pairs
Q3: How do you create custom pipe in Angular?
Answer To create a custom pipe:
- Generate using CLI:
ng g p pipes/my-custom-pipe
- Implement the PipeTrasform interface:
import { pipe, PipeTransform } from '@angular/core';
@pipe({
name: 'myCustomPipe',
standalone: true
})
export class MyCustomPipe implements PipeTransform {
trasform(value: any, ...args: any[]): any{
// Transformation Logic
return TransformValue;
}
}
- Use in Template:
{{someValue | myCustomPipe }}
Q4: What is difference between Pure and Impure Pipes?
Answer
Pure Pipe (Default):
- Called only when input value or refrence changes
- More performant
- Default behaviour (pure: true)
- Example: Changes to primitives or new object refrences
Impure Pipes:
- Called on every change detection cycle
- Less performent but detects changes with object/array
- must explicitly set pure: false
- Use when you need to detect internal changes in objects/arrays
// Pure pipe
@pipe({name: 'purePipe', pure: true})
// ImPure pipe
@pipe({name: 'impurePipe', pure: false})
Q5: What is Async pipe and why is it useful?
Answer: The asyncPipe subscribes to observables or Promised and returns the latest emitted value. It automatically handles subscription and unsubscription, preventing memory leaks.
Benefits:
- Automatic subscription managment
- Prevents memory leaks
- Cleaner code ( no munal subscribe/unsubscribe)
- Works with both Observable and Promises
Example:
//Component
data$ = this.http.get('/api/data');
//Template
<div>{{data$ | async | json }} </div>
Intermediate Level Questions
Q6: How can you pass parameter to a pipe?
Answer: Parameters are passed using colon (:) after the pipe name:
// Single parameter
{{ value | pipeName: parameter }}
//Mutiple parameter
{{value | pipeName: param1: param2: param3 }}
// Example
{{today | date: 'dd/mm/yyyy'}}
{{ price | currency: 'EUR': 'symbol':'1.2-2 '}}
Custom pipe with parameters
@pipe({ name: 'power'})
export class PowerPipe implements PipeTransform {
transform(value: number, exponent: number =1): number {
return Math.pow(value, exponent);
}
}
// Usage: {{2 | power: 3}} // Output: 8
Q7: Can you chain multiple pipes together? Provide an example.
Answer: Yes, pipes can be chained. The Output of one pipe become the input of the next pipe. They are executed from left to right.
Example:
//Component
birthdaty = new Date(1990, 5, 15);
//Tamplate
{{birthday | date:'full' | uppercase }}
//Output: FRIDAY, JUNE 15, 1990
{{user$ | async | json | slice:0:100 }}
// First resolve observable, converts to json , then slices
Q8: What happens if you use multiple async pipes with the same observable?
Answer: Each async pipe cretes a seperate subscription to the observable, which can lead to:
- Multiple Http requests (if it's an HTTP observables)
- Performance issues
- Inconsistent data
Solution 1: Use as keyword
<ng-container *ngIf="user$ | async as user">
<div>{{user.name}}</div>
<div>{{user.email}}</div>
</ng-container>
Solution 2: Use shareReplay operator
user$ = this.http.get('/api/user').pipe(shareReplay(1));
Q9: How would you implement a filter pipe? Why might it be problematic?
Answer:
Implementation:
@pipe({
name:'filter',
pure: false // Must be impure to detect array changes
})
export class FilterPipe implements PipeTransform {
transform(items: any[], searchText: string): any[]{
if(!items || !searchText){
return items;
}
return items.filter(item =>
item.toLowerCase().includes(searchText.tolowerCase())
);
}
}
Problems:
- Performance: - Impure pipes run on every change detection cycle
- Frequent Call: - Can be called hundreds of items per second
- Angular Bast Practice: Angular discourage filter in templates
Better Alternative:
// In Component
filteredItems : any[] = [];
filterItems(searchText: string){
this.filteredItems = this.items.filter(items =>
item.tolowerCase().includes(searchText.tolowerCase())
);
}
Q10: Explain the transform menthod signature in PipeTransforma interface.
Answer: The transform method is the core of any pipes:
transform(value: any, ...args: any[]): any
Parameters: value:: The input value to transform (required) ...arg: Rest parameter for additional arguments (optional)
Example:
@pipe({ name: 'formatName'})
export class FormateNamePipe implements PipeTransform {
transfrom (
value: string, // required input
formate?: 'upper'|'lower' | 'title', //optional param1
prefix? : string //optional param2
): string {
if(!value) return value;
let result = value;
if(format === 'upper') result = value.toUpperCase();
else if(format === 'lower') result = value.tolowerCase();
else if(format ==='title') result = value.charAt(0).toupperCase()+value.slice(1);
return prefix? `${prefix} ${result}`: result;
}
}
// Usage
{{name | formatName: 'upper':'Mr.' }}
Advance Level Questions
Q11: How do pure pipe dectect changes? Explain the mechanism.
Answer: Pure pipes use Angular's change detection to determaine if the input has changed:
For Primitives (string, Number, Boolean):
- Use strict equality (
===) - New value trigers the pipe
For Object and Arrays:
- Compares object refrences, not content
- Only triggers when reference changes (new object/array)
Example:
export class AppComponent {
items = [1,2,3];
//❌ Won't trigger pure pipe (same reference)
addItem(){
this.items.push(4);
}
//✅ will trigger pure pipe (new refrence)
addItemImmutably(){
this.items = [...this.items, 4];
}
}
Change Detection Flow
1. Angualr runs change detection
2. Checks if input reference changed (for objects/arrays)
3. If changed -> calls pipe's transform method
4. if unchanged -> returns cached result
Q12: what are the performance implementations of using impure pipes?
Answer: Performance Impact:
- Called Frequently: Runs on every change detection cycle (potentially hundreds of itmes per second)
- CPU Intensive: Can cause performance degrafation with complex transformations
- UI Lag: My cause jank or stuttering in the UI
Example Scenario:
@pipe({ name: 'expensiveFilter', pure: false})
expoer class ExpensiveFilter implements PipeTransforms {
transform(items: any[], filter: string): any[]{
console.log('Pipe Colled') // will log constantly
// Expensive operation runs repeatedly
return items.filter(item =>
this.complexCalculation(item, filter)
);
}
}
Optimization Strategies:
- Use Pure pipes: Update data immutably
this.items = [...this.items, newItem];
- Move logic to component:
// In Component
filterItem$ = combineLatest([
this.items$,
this.searchTerm$
]).pipe(
map(([items, term]) => items.filter(item => item.includes(term)))
);
- Memoization: Cache results
private cache = new Map();
transform(item: any[], filter: string): any[]{
const key = JSON.stringify({items, filter});
if(this.cache.has(key)){
return this.cache.get(key);
}
const result = items.filter(item => item.include(filter));
this.cache.set(key, result);
return result;
}
Q13: How would you implement a custom async pipe?
Answer:
important { Pipe, PipeTransform, ChangeDecetorRef, OnDestroy } from '@angular/core';
import {Observable, subscription } from 'rxjs';
@pipe({
name: 'customAsync',
pure:false,
stanalone: true
})
export class CustomAsyncPipe implements PipeTransform, OnDestroy {
private subscription: Subscription | null = null;
private latestValue: any = null;
private observable: Observable<any> |null = null;
constructor( private cdr: ChangeDecetorRef){}
transform(observable: Observable<any>): any {
if(!this.observable){
// First time or different observable
this.observable = observable;
this.subscribe(observable);
} else if(observable !==this.observable){
//Observable changed
this.dispose();
this.observable=observable;
this.subscribe(observable);
}
return this.latestValue;
}
private subscribe(observable: Observable<any>){
this.subscription = observable.subscribe({
next: (value) => {
this.latestValue = value;
this.cdr.markForCheck(); // Trigger change detection
},
error: (error) => {
console.log('Custom async pipe error: ', error);
}
});
}
private dispose(){
if(subscription) {
this.subscription.unsubscribe();
this.subscription = null;
}
this.latestValue = null;
}
ngDestroy(){
this.dispose();
}
}
Key Concepts:
- Must be impure (pure: false) to detect observable emmissions
- Needs ChangeDetectorRef to trigger view updates
- Implements OnDestroy for Cleanup
- Handles observable changes
Q14: How would you test a custom pipe?
answer:
Q15: What is the difference between using a pipe and a method call in a template?
Answer:
Pipe
{{ value | myPipe }}
Method call:
{{ transformValue(value) }}
Key Difference:
| Aspect | Pipe | Method call |
|---|---|---|
| Caching | Pure Pipe cache results | Called every change detection |
| Performance | Batter (for pure pipe) | worse (called repeatedly) |
| Reusability | Highly reusable | Component-specific |
| Testing | Easy to test in isolation | Tested with component |
| Readability | More declarative | more imerative |
| Change Detection | Optimized | Always called |
Performance Example:
// Component
export class AppComponent {
counter = 0;
// This will be called on EVERY change detection
transform(value: string): string {
console.log('Method called: ', ++this.counter);
return valure.toUpperCase();
}
}
// Template
<div>{{transform(name)}}</div>
<!--Console: method called: 1,2,3,4, ... (continuesly)-->
//with Pipe
<div>{{name | upperCase}}</div>
<!--Called only when 'name' changes-->
Best Practice:
- ✅ Use Pipe for data trasformation in template
- ❌ Avoid method calls in templates for transformations
- ✅ Use component menthods only for event handlers
Q16: How would you implement a memoization strategy for an expensive pipe?
Answer:
Q17: How do pipes work with OnPush change detection strategy?
Answer:
OnPush Change Detection: Component with OnPush strategy only check for changes when:
- Input reference change
- Event is triggered
- Observable emits (via async pipe)
- Manual change detection
Pipe with onPush:
@component({
selector: 'app-user',
template: `
<div>{{user.name | uppercase }}</div>
<div?> {{userData$ | async | json }}</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserComponent {
@input(): user: User; // Pure pipe works when reference changes
userData$ = this.service.getUser(); // async pipe triggers detection
}
Key Points:
- Pure pipes + OnPush = perfect match
// perent component
updateUser(){
//✅ Creates new reference, triggers onPush
this.user = {...this.user, name: 'New Name'};
}
// Child with OnPush
{{use.name | uppercase }} //will update
- ImPure pipes + OnPush =Issues
@component ({
template: `{{items | impureFilter: searchTerm }}`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyComponent {
items = [1,2,3];
searchTerm = '';
//❌ won't trigger OnPush even though pipe is impure
updateSearch(term: string){
this.searchTerm = term;
}
}
- Async Pipe + OnPush = Automatic Updates
@component({
template: `{{data$ | async}}`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyComponent {
data$ = this.service.getData();
//Async pipe automatically calls markForCheck()
}
Solution for Impure Pipe:
import { ChangeDectorRef } from '@angular/core';
@component({
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MyComponent {
constructor( private cdr: ChangeDectorRef){}
updateData(){
// Manually trigger change detection
this.cdr.markForChange();
}
}
Q18: Explain lifecycle of a pipe in Angular.
Angular:
Pipe Lifecycle:
@pipe({name: 'lifecycle'})
export class LifecyclePipe implements PipeTransform, OnDestroy {
private callCount =0;
constructor(){
console.log('1. Pipe constructed');
}
transform(value: any): any {
console.log(`2. Transform colled (${++this.callCount} times)`);
return value;
}
ngOnDestroy(){
console.log('3. Pipe destroyed');
}
}
Lifecylcle stages:
- Construction
- Pipe intance created when first used
- One intance per pipe usege in template
- Shared accross change detection cycle
- Tramsform Method calls:
- Pure Pipe: Called when input/reference changes
- Impure Pipe: Called on every change detection
- Can be called multiple times per detection cycle
- Destruction
- When component is destroyed
- Implements OnDestroy for cleanup
- Unsubcribe observables, clear caches
Example with multiple ueases:
@component({
template: `
<div?> {{value1 | lifecycle }} </div> <!--New instance-->
<div?> {{value2 | lifecycle }} </div> <!--New instance-->
<div?> {{value1 | lifecycle }} </div> <!--same a first-->
`
})
export class AppComponent{
value1 = 'Hellow';
value2 = 'World';
}
// Creates 2 pipe instances (one for eanch uniqe usage location)
Async Pipe Lifecycle:
@pipe({name: 'customAsync', pure: false})
export class CustomeAsyncPipe implements PipeTransform, OnDestroy {
transform(observable: Observable<any>): any {
// Subscribe on first call
if(!this.subscription){
this.subscription = observable.subscribe(/*....*/);
}
return this.latestValue;
}
}
ngOnDestroy(){
//Cleanup on Destroy
if(this.subscription){
this,subscription.unsubscribe();
}
}
Q19: How would you handle errors in a pipe?
Answer:
1. Try-Catch Block
@pipe({ name: 'safeTransform'})
export class SafeTransformPipe implements PipeTransform {
transform(value: any): any {
try{
// Risky transformation
return JSON.parse(value);
}catch(error){
console.log('Pipe error:', error);
return null; // Return default value
}
}
}
2. Null/Undefined Checks:
@pipe({ name: 'safeName' })
export class SafeNamePipe implements PipeTransfrom {
transform(value: any ): any {
if(value === null || value === undefined){
return 'N/A';
}
if(typeof value !== 'string'){
console.warn('Expected string, got:', typeof value);
return String(value);
}
return value.toUpperCase();
}
}
3. Error service Integration:
@pipe({ name: 'withErrorHandling' })
export class withErrorHandlingPipe implements PipeTransform {
constructor(private errorService: ErrorService){}
transform(value: any, fallback: any = null): any {
try{
// Complex transformation
return this.complexTransform(value);
}catch (error) {
// log to error service
this.errorService.logError({
message: 'pipe transform failed',
error,
value
});
return fallback;
}
}
private complexTransform(value: any): any {
// Your transform logic
return value;
}
}
4. Async Pipe Error Handling
@component ({
template:
`
<div *ngIf=*data$ | async as data; else loading>
{{data | json}}
</div>
<ng-template #loading>
<div *ngIf= "error$ | async as error">
Error: {{error}}
</div>
<div *ngIf="!(error$ | async )">
Loading...
</div>
</ng-template>
`
})
export class AppComponent{
error$ = new Subject<string>();
data$ = this.http.get('/api/data').pipe(
catchError(error => {
this.error$.next(error.massage);
return of(null);
})
);
}
5. Custom safe pipe Decotator:
export function SafePipe(fallbackValue: any = null){
return function (
target: any,
propertyKey: string,
descriptor:PropertyDescriptor
){
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]){
try{
return originalMethod.apply(this, args);
}catch(error){
Console.log(`Error in pipe ${target.constructor.name}: `, error);
return fallback;
}
};
return descriptor;
};
}
// Usage
@Pipe ({name: 'protected'})
export class ProtectedPipe implements PipeTransform {
@SafePipe ('Error occured')
transform(value: any): any {
// Risky operation
return value.dangerousMethod();
}
}
Q20: what are some real-world use casees for custom pipes? Provide examples.
Answer:
1. Phone Number formating:
@pipe({name: 'phoneNumber' })
export class PhoneNumberPipe implements PipeTransform {
transform(value: string, country: string: ='US'): String {
if(!value) return value;
const cleaned = value.replace(/\D/g, '');
if(country ==='US' && Cleaned.length ===10){
return `(${cleaned.slice(0,3)}) ${cleaned.slice(3,6)}-${cleand.slice(6)}`;
}
return value;
}
}
// Usege: {{'1234567890' | phoneNumber }}
// Output: (123) 456-7890
File size formating:
@Pipe({name: 'fileSize' })
export class FileSizePipe implements PipeTransform {
transform(bytes: number, decimals: number=2): string {
if(bytes===0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes)/ Math.log(k));
return (
parseFloat((bytes/ Math.pow(k,i)).toFixed(decimals)) + ''+size[i]
);
}
}
// Usege: {{1536 | filesize}}
// Output : 1.5 KB
3. Relative Time (Time Ago)
@pipe({name: 'timeAgo'})
export class TimeAgoPipe implements PipeTransform {
transform(value: Date | string): string {
const data = value instanceof Date? value: new Date(value);
const seconds = Math.floor((+new Date() - +date) / 1000);
const intervals: { [key: string]: number} = {
year: 31536000,
month: 2592000,
week: 86400,
day: 86400,
hour: 3600,
minute: 60,
second: 1
};
for(const [name, secondInterval] or Object.entries(intervals)) {
const interval = Math.floor(seconds / secondsInInterval);
if(interval>=1){
return interval ===1
? `${interval} ${name} ago`
: `${interval} ${name}s ago`;
}
}
return 'just now';
}
}
// Usege: {{post.createdAt | timeAgo }}
// Outpput: 2 hours ago
4. Safe HTML
import{ DomSanitizer, safeHtml } from '@angular/platform-browser';
@pipe({ name: 'safeHtml'})
export class SafeHtmlPipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer){}
transform(value: string): SafeHTML {
return this.sanitizer.bypassSecurityTrustHtml(value);
}
}
// Usage: <div [innerHtml]= "htmlContent | safeHtml"></div>
5. Search Highlight
@pipe({ name: 'highlight'})
export class HighlightPipe implements PipeTransform {
trasform(value: string, searchTerm: string): string {
if(!searchTerm) return value;
const regex = new Regex(searchTerm, 'gi');
return value.replace(
regex,
match=> `<mark>${match}</mark>`
);
}
}
// Usage: <div [innerHtml]="text | highlight: searchTerm"></div>
6. Pluralize:
@pipe({ name: 'pluralize'})
export class PluralizePipe implements PipeTransform {
transform(count: number, singular: string, plural?: string): string {
const pluralForm = plural || singular +'s'
return count ===1 ? `${count} ${singular}`: `${count}: ${pluralForm}`;
}
}
// Usage: {{itemCount || pluralize: 'item'}}
// Output: 1 item or 5 items
7. Mask Sensitive Data:
@pipe({ name: 'mask'})
export class MaskPipe implements PipeTransform {
transform( value: string, visibleChars: number =4, maskChar: string = '*'): string {
if(!value) return value;
const visible = value.slice(-visibleChars);
const masked = maskChar.repeat(value.length- visibleChars);
return masked + visible;
}
}
// Usage: {{createdAt || mask:4}}
// Output: ***************1234
Summary
Key Takeways:
- Pipes transform data in tamplate without modifying the source
- Pure pipes are more performent and should be preferred
- Async pipe handles subscriptions automatically
- Custom pipe sould be simple, focused, and resuable
- Avoid filtering/sorting in templates, do it in components
- Test Pipe in isolation for batter maintainability
- Memoization can optimize expencsive pipes
- Error handiling prevents pipe failures form breaking UI
Common mistake to Avoid:
- ❌ Using impure pipe for expensive operation
- ❌ Multiple async pipes on the same observable
- ❌ Complex business logic in pipe
- ❌ Mutating input values in pipe
- ❌ Not handling null/ undefined values
- ❌ Filtering large arrays with impure pipes
Best practices
- ✅ Keep pipes pure when possible
- ✅ Use asyc pipe for observables
- ✅ Handle error gracefully
- ✅ Write unit test for custom pipe -✅ Use descriptive pipe name
- ✅ Document pipe parameter -✅ Consiter performance implications
Comments
Post a Comment