Skip to content

Angular Pipes

Pipes in Angular are a powerful feature for transforming data in templates. They take data as input and transform it into a desired output format for display. Angular provides many built-in pipes for common transformations, and you can also create custom pipes for specific needs.

Understanding Pipes

Pipes are simple functions that accept an input value and return a transformed value. They are used in templates with the pipe operator (|):

{{ expression | pipeName }}
{{ expression | pipeName:parameter }}
{{ expression | pipeName:parameter1:parameter2 }}

Basic Pipe Usage

@Component({
  selector: 'app-pipe-demo',
  template: `
    <div class="pipe-demo">
      <h2>Basic Pipe Examples</h2>

      <!-- Original value -->
      <p>Original: {{ message }}</p>

      <!-- Transformed values -->
      <p>Uppercase: {{ message | uppercase }}</p>
      <p>Lowercase: {{ message | lowercase }}</p>
      <p>Title Case: {{ message | titlecase }}</p>

      <!-- Number formatting -->
      <p>Number: {{ price | currency:'USD':'symbol':'1.2-2' }}</p>
      <p>Percent: {{ ratio | percent:'1.1-2' }}</p>

      <!-- Date formatting -->
      <p>Date: {{ today | date:'medium' }}</p>
      <p>Custom Date: {{ today | date:'dd/MM/yyyy HH:mm' }}</p>
    </div>
  `
})
export class PipeDemoComponent {
  message = 'hello angular pipes';
  price = 1234.56;
  ratio = 0.1234;
  today = new Date();
}

Built-in Pipes

Angular provides a comprehensive set of built-in pipes for common transformations:

String Pipes

@Component({
  selector: 'app-string-pipes',
  template: `
    <div class="string-pipes">
      <h3>String Transformation Pipes</h3>

      <div class="example">
        <strong>Original:</strong> {{ text }}
      </div>

      <div class="example">
        <strong>UpperCase:</strong> {{ text | uppercase }}
      </div>

      <div class="example">
        <strong>LowerCase:</strong> {{ text | lowercase }}
      </div>

      <div class="example">
        <strong>TitleCase:</strong> {{ text | titlecase }}
      </div>

      <div class="example">
        <strong>Slice (0,10):</strong> {{ text | slice:0:10 }}
      </div>

      <div class="example">
        <strong>Slice (5):</strong> {{ text | slice:5 }}
      </div>

      <!-- JSON Pipe for debugging -->
      <div class="example">
        <strong>JSON:</strong> {{ user | json }}
      </div>
    </div>
  `
})
export class StringPipesComponent {
  text = 'Angular Pipes are POWERFUL tools for data transformation';
  user = {
    name: 'John Doe',
    age: 30,
    email: 'john@example.com',
    preferences: {
      theme: 'dark',
      language: 'en'
    }
  };
}

Number Pipes

@Component({
  selector: 'app-number-pipes',
  template: `
    <div class="number-pipes">
      <h3>Number Formatting Pipes</h3>

      <!-- Decimal Pipe -->
      <div class="example-group">
        <h4>Decimal Pipe</h4>
        <p>Original: {{ number }}</p>
        <p>Default: {{ number | number }}</p>
        <p>Min 1, Max 2 decimals: {{ number | number:'1.1-2' }}</p>
        <p>Min 3, Max 3 decimals: {{ number | number:'1.3-3' }}</p>
        <p>No decimals: {{ number | number:'1.0-0' }}</p>
      </div>

      <!-- Currency Pipe -->
      <div class="example-group">
        <h4>Currency Pipe</h4>
        <p>Default USD: {{ price | currency }}</p>
        <p>EUR Symbol: {{ price | currency:'EUR':'symbol' }}</p>
        <p>EUR Code: {{ price | currency:'EUR':'code' }}</p>
        <p>Custom Symbol: {{ price | currency:'EUR':'symbol':'1.0-0' }}</p>
        <p>Different Locale: {{ price | currency:'USD':'symbol':'1.2-2':'de' }}</p>
      </div>

      <!-- Percent Pipe -->
      <div class="example-group">
        <h4>Percent Pipe</h4>
        <p>Default: {{ percentage | percent }}</p>
        <p>1 decimal: {{ percentage | percent:'1.1-1' }}</p>
        <p>3 decimals: {{ percentage | percent:'1.3-3' }}</p>
        <p>No decimals: {{ percentage | percent:'1.0-0' }}</p>
      </div>
    </div>
  `
})
export class NumberPipesComponent {
  number = 1234.5678;
  price = 1234.56;
  percentage = 0.1234;
}

Date Pipes

@Component({
  selector: 'app-date-pipes',
  template: `
    <div class="date-pipes">
      <h3>Date Formatting Pipes</h3>

      <div class="example-group">
        <h4>Predefined Formats</h4>
        <p>Original: {{ currentDate }}</p>
        <p>Short: {{ currentDate | date:'short' }}</p>
        <p>Medium: {{ currentDate | date:'medium' }}</p>
        <p>Long: {{ currentDate | date:'long' }}</p>
        <p>Full: {{ currentDate | date:'full' }}</p>
        <p>Short Date: {{ currentDate | date:'shortDate' }}</p>
        <p>Medium Date: {{ currentDate | date:'mediumDate' }}</p>
        <p>Long Date: {{ currentDate | date:'longDate' }}</p>
        <p>Full Date: {{ currentDate | date:'fullDate' }}</p>
        <p>Short Time: {{ currentDate | date:'shortTime' }}</p>
        <p>Medium Time: {{ currentDate | date:'mediumTime' }}</p>
      </div>

      <div class="example-group">
        <h4>Custom Formats</h4>
        <p>dd/MM/yyyy: {{ currentDate | date:'dd/MM/yyyy' }}</p>
        <p>MM-dd-yyyy: {{ currentDate | date:'MM-dd-yyyy' }}</p>
        <p>yyyy-MM-dd HH:mm:ss: {{ currentDate | date:'yyyy-MM-dd HH:mm:ss' }}</p>
        <p>EEEE, MMMM d, y: {{ currentDate | date:'EEEE, MMMM d, y' }}</p>
        <p>h:mm a: {{ currentDate | date:'h:mm a' }}</p>
        <p>HH:mm:ss: {{ currentDate | date:'HH:mm:ss' }}</p>
      </div>

      <div class="example-group">
        <h4>Different Timezones</h4>
        <p>UTC: {{ currentDate | date:'medium':'UTC' }}</p>
        <p>New York: {{ currentDate | date:'medium':'America/New_York' }}</p>
        <p>Tokyo: {{ currentDate | date:'medium':'Asia/Tokyo' }}</p>
        <p>London: {{ currentDate | date:'medium':'Europe/London' }}</p>
      </div>

      <div class="example-group">
        <h4>Different Locales</h4>
        <p>US English: {{ currentDate | date:'full':'':'en-US' }}</p>
        <p>German: {{ currentDate | date:'full':'':'de-DE' }}</p>
        <p>French: {{ currentDate | date:'full':'':'fr-FR' }}</p>
        <p>Japanese: {{ currentDate | date:'full':'':'ja-JP' }}</p>
      </div>
    </div>
  `
})
export class DatePipesComponent {
  currentDate = new Date();
}

Collection Pipes

@Component({
  selector: 'app-collection-pipes',
  template: `
    <div class="collection-pipes">
      <h3>Collection Pipes</h3>

      <!-- KeyValue Pipe -->
      <div class="example-group">
        <h4>KeyValue Pipe</h4>
        <div>
          <strong>Object:</strong>
          <ul>
            <li *ngFor="let item of user | keyvalue">
              {{ item.key }}: {{ item.value }}
            </li>
          </ul>
        </div>

        <div>
          <strong>Object with Custom Sort:</strong>
          <ul>
            <li *ngFor="let item of user | keyvalue:customSort">
              {{ item.key }}: {{ item.value }}
            </li>
          </ul>
        </div>

        <div>
          <strong>Map:</strong>
          <ul>
            <li *ngFor="let item of userMap | keyvalue">
              {{ item.key }}: {{ item.value }}
            </li>
          </ul>
        </div>
      </div>

      <!-- Slice Pipe with Arrays -->
      <div class="example-group">
        <h4>Slice Pipe with Arrays</h4>
        <p>Original Array: {{ numbers }}</p>
        <p>First 3: {{ numbers | slice:0:3 }}</p>
        <p>Last 3: {{ numbers | slice:-3 }}</p>
        <p>Middle (2-5): {{ numbers | slice:2:5 }}</p>
        <p>From index 3: {{ numbers | slice:3 }}</p>
      </div>
    </div>
  `
})
export class CollectionPipesComponent {
  user = {
    name: 'John Doe',
    age: 30,
    email: 'john@example.com',
    city: 'New York'
  };

  userMap = new Map([
    ['id', 1],
    ['username', 'johndoe'],
    ['active', true],
    ['lastLogin', new Date()]
  ]);

  numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

  customSort = (a: any, b: any) => {
    return a.key.localeCompare(b.key);
  };
}

Pipe Parameters

Pipes can accept parameters to customize their behavior:

@Component({
  selector: 'app-pipe-parameters',
  template: `
    <div class="pipe-parameters">
      <h3>Pipe Parameters</h3>

      <!-- Single Parameter -->
      <p>Slice with single parameter: {{ text | slice:10 }}</p>

      <!-- Multiple Parameters -->
      <p>Slice with multiple parameters: {{ text | slice:5:15 }}</p>

      <!-- Currency with parameters -->
      <p>Currency USD: {{ amount | currency:'USD':'symbol':'1.2-2' }}</p>
      <p>Currency EUR: {{ amount | currency:'EUR':'code':'1.0-0' }}</p>

      <!-- Date with parameters -->
      <p>Date format: {{ date | date:'dd/MM/yyyy':'UTC':locale }}</p>

      <!-- Number with parameters -->
      <p>Number format: {{ number | number:'1.2-4':locale }}</p>

      <!-- Dynamic parameters -->
      <div class="controls">
        <label>
          Currency:
          <select [(ngModel)]="selectedCurrency">
            <option value="USD">USD</option>
            <option value="EUR">EUR</option>
            <option value="GBP">GBP</option>
            <option value="JPY">JPY</option>
          </select>
        </label>

        <label>
          Date Format:
          <select [(ngModel)]="selectedDateFormat">
            <option value="short">Short</option>
            <option value="medium">Medium</option>
            <option value="long">Long</option>
            <option value="dd/MM/yyyy">dd/MM/yyyy</option>
            <option value="MM-dd-yyyy HH:mm">MM-dd-yyyy HH:mm</option>
          </select>
        </label>
      </div>

      <div class="dynamic-results">
        <p>Dynamic Currency: {{ amount | currency:selectedCurrency:'symbol':'1.2-2' }}</p>
        <p>Dynamic Date: {{ date | date:selectedDateFormat }}</p>
      </div>
    </div>
  `
})
export class PipeParametersComponent {
  text = 'Angular Pipes with Parameters';
  amount = 1234.567;
  date = new Date();
  number = 12345.6789;
  locale = 'en-US';
  selectedCurrency = 'USD';
  selectedDateFormat = 'medium';
}

Chaining Pipes

You can chain multiple pipes together to apply multiple transformations:

@Component({
  selector: 'app-pipe-chaining',
  template: `
    <div class="pipe-chaining">
      <h3>Pipe Chaining</h3>

      <!-- String transformations -->
      <div class="example-group">
        <h4>String Chaining</h4>
        <p>Original: {{ message }}</p>
        <p>Slice + Uppercase: {{ message | slice:0:10 | uppercase }}</p>
        <p>Uppercase + Slice: {{ message | uppercase | slice:0:15 }}</p>
        <p>Titlecase + Slice: {{ message | titlecase | slice:0:20 }}</p>
      </div>

      <!-- Number and currency chaining -->
      <div class="example-group">
        <h4>Number Chaining</h4>
        <p>Original: {{ price }}</p>
        <p>Currency + Uppercase: {{ price | currency:'USD' | uppercase }}</p>
        <p>Number + Slice: {{ largeNumber | number:'1.0-0' | slice:0:5 }}</p>
      </div>

      <!-- Date chaining -->
      <div class="example-group">
        <h4>Date Chaining</h4>
        <p>Original: {{ today }}</p>
        <p>Date + Uppercase: {{ today | date:'full' | uppercase }}</p>
        <p>Date + Slice: {{ today | date:'full' | slice:0:20 }}</p>
        <p>Date + Titlecase: {{ today | date:'EEEE, MMMM d, y' | titlecase }}</p>
      </div>

      <!-- Complex chaining -->
      <div class="example-group">
        <h4>Complex Chaining</h4>
        <p>User JSON + Slice: {{ user | json | slice:0:50 }}</p>
        <p>Array + Slice + JSON: {{ numbers | slice:0:5 | json }}</p>
      </div>
    </div>
  `
})
export class PipeChainingComponent {
  message = 'pipe chaining demonstration in angular';
  price = 1234.56;
  largeNumber = 1234567890;
  today = new Date();
  user = {
    name: 'John Doe',
    email: 'john@example.com',
    preferences: { theme: 'dark', notifications: true }
  };
  numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
}

Creating Custom Pipes

Basic Custom Pipe

// capitalize.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'capitalize'
})
export class CapitalizePipe implements PipeTransform {
  transform(value: string): string {
    if (!value) return value;

    return value.split(' ')
      .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
      .join(' ');
  }
}

Custom Pipe with Parameters

// truncate.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'truncate'
})
export class TruncatePipe implements PipeTransform {
  transform(
    value: string,
    limit: number = 50,
    trail: string = '...'
  ): string {
    if (!value) return value;

    if (value.length <= limit) {
      return value;
    }

    return value.substring(0, limit) + trail;
  }
}

Advanced Custom Pipe

// filter.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'filter',
  pure: false // Make it impure to detect array changes
})
export class FilterPipe implements PipeTransform {
  transform(
    items: any[],
    searchTerm: string,
    property?: string
  ): any[] {
    if (!items || !searchTerm) {
      return items;
    }

    searchTerm = searchTerm.toLowerCase();

    return items.filter(item => {
      if (property) {
        return item[property]?.toString().toLowerCase().includes(searchTerm);
      } else {
        return JSON.stringify(item).toLowerCase().includes(searchTerm);
      }
    });
  }
}

Custom Pipe for Complex Transformations

// highlight.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';

@Pipe({
  name: 'highlight'
})
export class HighlightPipe implements PipeTransform {
  constructor(private sanitizer: DomSanitizer) {}

  transform(
    text: string,
    search: string,
    className: string = 'highlight'
  ): SafeHtml {
    if (!text || !search) {
      return text;
    }

    const regex = new RegExp(`(${search})`, 'gi');
    const highlighted = text.replace(
      regex,
      `<span class="${className}">$1</span>`
    );

    return this.sanitizer.bypassSecurityTrustHtml(highlighted);
  }
}

Using Custom Pipes

@Component({
  selector: 'app-custom-pipes',
  template: `
    <div class="custom-pipes">
      <h3>Custom Pipes Demo</h3>

      <!-- Capitalize Pipe -->
      <div class="example-group">
        <h4>Capitalize Pipe</h4>
        <p>Original: {{ text }}</p>
        <p>Capitalized: {{ text | capitalize }}</p>
      </div>

      <!-- Truncate Pipe -->
      <div class="example-group">
        <h4>Truncate Pipe</h4>
        <p>Original: {{ longText }}</p>
        <p>Truncated (default): {{ longText | truncate }}</p>
        <p>Truncated (20 chars): {{ longText | truncate:20 }}</p>
        <p>Truncated (custom trail): {{ longText | truncate:30:'...[more]' }}</p>
      </div>

      <!-- Filter Pipe -->
      <div class="example-group">
        <h4>Filter Pipe</h4>
        <input [(ngModel)]="searchTerm" placeholder="Search users...">
        <ul>
          <li *ngFor="let user of users | filter:searchTerm:'name'">
            {{ user.name }} - {{ user.email }}
          </li>
        </ul>
      </div>

      <!-- Highlight Pipe -->
      <div class="example-group">
        <h4>Highlight Pipe</h4>
        <input [(ngModel)]="highlightTerm" placeholder="Text to highlight...">
        <p [innerHTML]="sampleText | highlight:highlightTerm"></p>
      </div>
    </div>
  `,
  styles: [`
    .highlight {
      background-color: yellow;
      font-weight: bold;
    }
    .example-group {
      margin-bottom: 2rem;
      padding: 1rem;
      border: 1px solid #ddd;
      border-radius: 4px;
    }
  `]
})
export class CustomPipesComponent {
  text = 'hello world from angular';
  longText = 'This is a very long text that needs to be truncated because it contains too much information for the available space in the user interface.';
  searchTerm = '';
  highlightTerm = '';
  sampleText = 'Angular pipes are a great way to transform data in templates. They provide a clean and declarative way to format and modify data for display purposes.';

  users = [
    { name: 'John Doe', email: 'john@example.com' },
    { name: 'Jane Smith', email: 'jane@example.com' },
    { name: 'Bob Johnson', email: 'bob@example.com' },
    { name: 'Alice Brown', email: 'alice@example.com' }
  ];
}

Pure vs Impure Pipes

Pure Pipes (Default)

Pure pipes are executed only when Angular detects a pure change to the input value:

// A pure pipe (default behavior)
@Pipe({
  name: 'purePipe',
  pure: true // This is the default
})
export class PurePipe implements PipeTransform {
  transform(value: any[]): any[] {
    console.log('Pure pipe executed'); // Called only when array reference changes
    return value.filter(item => item.active);
  }
}

Impure Pipes

Impure pipes are executed on every change detection cycle:

// An impure pipe
@Pipe({
  name: 'impurePipe',
  pure: false
})
export class ImpurePipe implements PipeTransform {
  transform(value: any[]): any[] {
    console.log('Impure pipe executed'); // Called on every change detection
    return value.filter(item => item.active);
  }
}

Comparison Example

@Component({
  selector: 'app-pure-impure',
  template: `
    <div class="pure-impure-demo">
      <h3>Pure vs Impure Pipes</h3>

      <button (click)="addItem()">Add Item</button>
      <button (click)="toggleFirstItem()">Toggle First Item</button>
      <button (click)="updateCounter()">Update Counter ({{ counter }})</button>

      <div class="results">
        <h4>Pure Pipe Result:</h4>
        <ul>
          <li *ngFor="let item of items | purePipe">
            {{ item.name }} - {{ item.active ? 'Active' : 'Inactive' }}
          </li>
        </ul>

        <h4>Impure Pipe Result:</h4>
        <ul>
          <li *ngFor="let item of items | impurePipe">
            {{ item.name }} - {{ item.active ? 'Active' : 'Inactive' }}
          </li>
        </ul>
      </div>
    </div>
  `
})
export class PureImpureComponent {
  counter = 0;
  items = [
    { name: 'Item 1', active: true },
    { name: 'Item 2', active: false },
    { name: 'Item 3', active: true }
  ];

  addItem(): void {
    // This creates a new array reference, so pure pipe will execute
    this.items = [...this.items, {
      name: `Item ${this.items.length + 1}`,
      active: Math.random() > 0.5
    }];
  }

  toggleFirstItem(): void {
    // This modifies the array in place, pure pipe won't execute
    this.items[0].active = !this.items[0].active;
  }

  updateCounter(): void {
    // This doesn't affect the array, but impure pipe will still execute
    this.counter++;
  }
}

Async Pipe

The async pipe is a special built-in pipe for handling asynchronous data:

// data.service.ts
import { Injectable } from '@angular/core';
import { Observable, interval, of, delay } from 'rxjs';
import { map, startWith } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  // Observable that emits current time every second
  getCurrentTime(): Observable<Date> {
    return interval(1000).pipe(
      map(() => new Date())
    );
  }

  // Observable that simulates API call
  getUsers(): Observable<any[]> {
    const users = [
      { id: 1, name: 'John Doe', email: 'john@example.com' },
      { id: 2, name: 'Jane Smith', email: 'jane@example.com' },
      { id: 3, name: 'Bob Johnson', email: 'bob@example.com' }
    ];

    return of(users).pipe(delay(2000));
  }

  // Observable that emits numbers
  getNumbers(): Observable<number[]> {
    return interval(1000).pipe(
      map(i => Array.from({ length: i + 1 }, (_, index) => index + 1)),
      startWith([])
    );
  }
}
@Component({
  selector: 'app-async-pipe',
  template: `
    <div class="async-pipe-demo">
      <h3>Async Pipe Demo</h3>

      <!-- Simple observable -->
      <div class="example-group">
        <h4>Current Time</h4>
        <p>{{ currentTime$ | async | date:'medium' }}</p>
      </div>

      <!-- Observable with loading state -->
      <div class="example-group">
        <h4>Users (with loading state)</h4>
        <div *ngIf="users$ | async as users; else loading">
          <ul>
            <li *ngFor="let user of users">
              {{ user.name }} - {{ user.email }}
            </li>
          </ul>
        </div>
        <ng-template #loading>
          <p>Loading users...</p>
        </ng-template>
      </div>

      <!-- Multiple async pipes (inefficient) -->
      <div class="example-group">
        <h4>Multiple Async Pipes (Inefficient)</h4>
        <div *ngIf="users$ | async">
          <p>User count: {{ (users$ | async)?.length }}</p>
          <p>First user: {{ (users$ | async)?.[0]?.name }}</p>
        </div>
      </div>

      <!-- Single async pipe with alias (efficient) -->
      <div class="example-group">
        <h4>Single Async Pipe with Alias (Efficient)</h4>
        <div *ngIf="users$ | async as users">
          <p>User count: {{ users.length }}</p>
          <p>First user: {{ users[0]?.name }}</p>
          <ul>
            <li *ngFor="let user of users">{{ user.name }}</li>
          </ul>
        </div>
      </div>

      <!-- Dynamic numbers -->
      <div class="example-group">
        <h4>Dynamic Numbers</h4>
        <p>Numbers: {{ numbers$ | async | json }}</p>
        <p>Sum: {{ (numbers$ | async)?.reduce((a, b) => a + b, 0) }}</p>
      </div>

      <!-- Promise support -->
      <div class="example-group">
        <h4>Promise Support</h4>
        <p>Promise result: {{ promiseData | async }}</p>
      </div>
    </div>
  `
})
export class AsyncPipeComponent implements OnInit {
  currentTime$!: Observable<Date>;
  users$!: Observable<any[]>;
  numbers$!: Observable<number[]>;
  promiseData!: Promise<string>;

  constructor(private dataService: DataService) {}

  ngOnInit(): void {
    this.currentTime$ = this.dataService.getCurrentTime();
    this.users$ = this.dataService.getUsers();
    this.numbers$ = this.dataService.getNumbers();

    // Promise example
    this.promiseData = new Promise(resolve => {
      setTimeout(() => {
        resolve('Promise resolved after 3 seconds');
      }, 3000);
    });
  }
}

Advanced Pipe Techniques

Memoized Pipes for Performance

// memoized.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'memoized'
})
export class MemoizedPipe implements PipeTransform {
  private cache = new Map<string, any>();

  transform(value: any, ...args: any[]): any {
    const key = JSON.stringify([value, ...args]);

    if (this.cache.has(key)) {
      console.log('Cache hit');
      return this.cache.get(key);
    }

    console.log('Computing result');
    const result = this.expensiveOperation(value, ...args);
    this.cache.set(key, result);

    return result;
  }

  private expensiveOperation(value: any, ...args: any[]): any {
    // Simulate expensive computation
    return value?.toString().repeat(args[0] || 1);
  }
}

Conditional Pipes

// conditional.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'conditional'
})
export class ConditionalPipe implements PipeTransform {
  transform(
    value: any,
    condition: boolean,
    trueValue: any,
    falseValue: any = value
  ): any {
    return condition ? trueValue : falseValue;
  }
}

Safe Navigation Pipe

// safe.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'safe'
})
export class SafePipe implements PipeTransform {
  transform(value: any, path: string): any {
    if (!value || !path) return value;

    return path.split('.').reduce((obj, key) => {
      return obj && obj[key] !== undefined ? obj[key] : null;
    }, value);
  }
}

Usage of Advanced Pipes

@Component({
  selector: 'app-advanced-pipes',
  template: `
    <div class="advanced-pipes">
      <h3>Advanced Pipe Techniques</h3>

      <!-- Memoized Pipe -->
      <div class="example-group">
        <h4>Memoized Pipe</h4>
        <input [(ngModel)]="memoValue" placeholder="Enter text...">
        <input [(ngModel)]="repeatCount" type="number" placeholder="Repeat count...">
        <p>Result: {{ memoValue | memoized:repeatCount }}</p>
      </div>

      <!-- Conditional Pipe -->
      <div class="example-group">
        <h4>Conditional Pipe</h4>
        <label>
          <input type="checkbox" [(ngModel)]="showFullName"> Show Full Name
        </label>
        <p>Name: {{ user.firstName | conditional:showFullName:user.fullName:user.firstName }}</p>
      </div>

      <!-- Safe Navigation Pipe -->
      <div class="example-group">
        <h4>Safe Navigation Pipe</h4>
        <p>User Name: {{ user | safe:'profile.name' }}</p>
        <p>User Email: {{ user | safe:'profile.contact.email' }}</p>
        <p>Invalid Path: {{ user | safe:'invalid.path.here' }}</p>
      </div>
    </div>
  `
})
export class AdvancedPipesComponent {
  memoValue = 'test';
  repeatCount = 2;
  showFullName = false;

  user = {
    firstName: 'John',
    fullName: 'John Doe',
    profile: {
      name: 'John Doe',
      contact: {
        email: 'john@example.com'
      }
    }
  };
}

Performance Considerations

Pipe Performance Best Practices

// ✅ Good: Pure pipe for simple transformations
@Pipe({
  name: 'multiply',
  pure: true
})
export class MultiplyPipe implements PipeTransform {
  transform(value: number, factor: number): number {
    return value * factor;
  }
}

// ❌ Bad: Impure pipe for simple transformations
@Pipe({
  name: 'multiplyImpure',
  pure: false
})
export class MultiplyImpurePipe implements PipeTransform {
  transform(value: number, factor: number): number {
    console.log('Expensive calculation executed');
    return value * factor;
  }
}

// ✅ Good: Memoization for expensive operations
@Pipe({
  name: 'expensiveOperation'
})
export class ExpensiveOperationPipe implements PipeTransform {
  private cache = new Map();

  transform(value: any): any {
    if (this.cache.has(value)) {
      return this.cache.get(value);
    }

    const result = this.performExpensiveOperation(value);
    this.cache.set(value, result);
    return result;
  }

  private performExpensiveOperation(value: any): any {
    // Expensive computation here
    return value;
  }
}

Template Optimization

<!-- ❌ Bad: Multiple async pipe subscriptions -->
<div *ngIf="data$ | async">
  <p>Count: {{ (data$ | async)?.length }}</p>
  <p>First: {{ (data$ | async)?.[0] }}</p>
</div>

<!-- ✅ Good: Single async pipe with alias -->
<div *ngIf="data$ | async as data">
  <p>Count: {{ data.length }}</p>
  <p>First: {{ data[0] }}</p>
</div>

<!-- ✅ Good: Store in component property -->
<div *ngIf="data">
  <p>Count: {{ data.length }}</p>
  <p>First: {{ data[0] }}</p>
</div>

Best Practices

1. Choose the Right Pipe Type

// Use pure pipes for deterministic transformations
@Pipe({ name: 'capitalize', pure: true })
export class CapitalizePipe implements PipeTransform {
  transform(value: string): string {
    return value ? value.charAt(0).toUpperCase() + value.slice(1) : value;
  }
}

// Use impure pipes only when necessary
@Pipe({ name: 'dynamicFilter', pure: false })
export class DynamicFilterPipe implements PipeTransform {
  transform(items: any[], filterFn: (item: any) => boolean): any[] {
    return items?.filter(filterFn) || [];
  }
}

2. Error Handling

@Pipe({ name: 'safeTransform' })
export class SafeTransformPipe implements PipeTransform {
  transform(value: any, operation: string): any {
    try {
      switch (operation) {
        case 'upper':
          return value?.toString().toUpperCase();
        case 'lower':
          return value?.toString().toLowerCase();
        case 'reverse':
          return value?.toString().split('').reverse().join('');
        default:
          return value;
      }
    } catch (error) {
      console.error('Pipe error:', error);
      return value; // Return original value on error
    }
  }
}

3. Type Safety

interface User {
  id: number;
  name: string;
  email: string;
}

@Pipe({ name: 'userFilter' })
export class UserFilterPipe implements PipeTransform {
  transform(users: User[], searchTerm: string): User[] {
    if (!users || !searchTerm) {
      return users;
    }

    return users.filter(user =>
      user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
      user.email.toLowerCase().includes(searchTerm.toLowerCase())
    );
  }
}

4. Testing Pipes

// user-filter.pipe.spec.ts
import { UserFilterPipe } from './user-filter.pipe';

describe('UserFilterPipe', () => {
  let pipe: UserFilterPipe;
  const mockUsers = [
    { id: 1, name: 'John Doe', email: 'john@example.com' },
    { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
  ];

  beforeEach(() => {
    pipe = new UserFilterPipe();
  });

  it('should create an instance', () => {
    expect(pipe).toBeTruthy();
  });

  it('should filter users by name', () => {
    const result = pipe.transform(mockUsers, 'john');
    expect(result.length).toBe(1);
    expect(result[0].name).toBe('John Doe');
  });

  it('should filter users by email', () => {
    const result = pipe.transform(mockUsers, 'jane@example.com');
    expect(result.length).toBe(1);
    expect(result[0].email).toBe('jane@example.com');
  });

  it('should return all users when search term is empty', () => {
    const result = pipe.transform(mockUsers, '');
    expect(result.length).toBe(2);
  });

  it('should handle null/undefined inputs', () => {
    expect(pipe.transform(null as any, 'test')).toBeNull();
    expect(pipe.transform(mockUsers, null as any)).toBe(mockUsers);
  });
});

Summary

Angular pipes are powerful tools for data transformation in templates:

Key Concepts:

  • Built-in pipes: String, number, date, collection transformations
  • Custom pipes: Create reusable transformation logic
  • Pipe parameters: Customize pipe behavior
  • Pipe chaining: Combine multiple transformations
  • Pure vs Impure: Performance considerations
  • Async pipe: Handle asynchronous data

Best Practices:

  • Use pure pipes for deterministic transformations
  • Implement error handling in custom pipes
  • Use TypeScript for type safety
  • Cache expensive operations with memoization
  • Minimize use of impure pipes
  • Use async pipe with aliases to avoid multiple subscriptions
  • Write comprehensive tests for custom pipes

Performance Tips:

  • Prefer pure pipes over impure pipes
  • Use single async pipe with template aliases
  • Implement caching for expensive operations
  • Avoid complex logic in pipe transforms

Next, we'll explore Angular services and dependency injection patterns for managing application state and business logic.