Angular Pipes

Angular Pipes

posted in javascript on  •   •  • last updated on

Pipes: Chainable, declarative display-value transformations to use in your Html.

This post covers:

  • All Builtin Angular pipes (json, async, string, array, i18n)
  • How to install different locales (currency, decimal, date, percent)
  • How to generate, implement and test your custom pipes
  • Some examples of custom pipes (ngx-pipes and angular-pipes)

An example:

<!-- This does exactly what you'd think -->
{{ value | uppercase }}

Builtin Pipes

JsonPipe (Impure)

Quick object dump with the impure JsonPipe.

Impure: Angular executes an impure pipe during every component change detection cycle. An impure pipe is called often, as often as every keystroke or mouse-move.

JsonPipe API

Example usage:

<pre>{{ {number: 7} | json }}</pre>

<!-- Combine with the AsyncPipe to dump Observables -->
<code>{{ observable$ | async | json }}</code>

Inject in a Component:

import { CurrencyPipe, formatCurrency } from '@angular/common';

@Component({
  providers: [CurrencyPipe]
})
export class AppComponent {
  constructor(private cp: CurrencyPipe) {
    this.cp.transform(450.657, 'EUR', 'symbol', '0.2-2', 'fr'); // 450,66 €

    // Use the formatCurrency instead! (formatDate, formatNumber, ...)
    formatCurrency(450.657, 'fr', 'EUR', 'symbol', '0.2-2');
  }
}

String Casing Pipes

UpperCasePipe , LowerCasePipe and TitleCasePipe.

lowercase => {{ 'LOWERCASE' | lowercase }}

UPPERCASE => {{ 'uppercase' | uppercase }}

This Is Title Case => {{ 'tHIs is tiTLE CaSe' | titlecase }}

Locale Sensitive Pipes

Used by DatePipe, CurrencyPipe, DecimalPipe and PercentPipe.

Setup

Official i18n docs

By default, Angular comes with the en-US locale only. Import a different locale in your app.module.ts:

import { registerLocaleData } from '@angular/common';
import { LOCALE_ID, NgModule } from '@angular/core';
import localeFr from '@angular/common/locales/fr';

registerLocaleData(localeFr, 'fr');

@NgModule({
  providers: [
    {provide: DEFAULT_CURRENCY_CODE, useValue: 'EUR'}, // default: USD
    {provide: LOCALE_ID, useValue: 'fr'}, // default: en-US
    // default dateFormat: mediumDate
    // default timezone: user local system timezone
    {provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: {dateFormat: 'MM/dd/yy', timezone: '-1200'}},
  ],
})
export class AppModule { }

Or without module:

import { ApplicationConfig } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    {provide: LOCALE_ID, useValue: 'fr'},
    {provide: DEFAULT_CURRENCY_CODE, useValue: 'EUR'},
    {provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: {dateFormat: 'MM/dd/yy', timezone: '-1200'}},
  ]
};

bootstrapApplication(AppComponent, appConfig);

CurrencyPipe

CurrencyPipe API

Outside html templates, use formatCurrency() instead!

<!-- Signature -->
{{ value_expression | currency [ : currencyCode [ : display [ : digitsInfo [ : locale ] ] ] ] }}

<!-- Examples -->
€1,500.95 => {{ 1500.953 | currency:'EUR':'symbol' }}

003,96 EUR => {{ 3.955 | currency:'EUR':'code':'3.2-2':'fr' }}

Parameters:

  • currencyCode: USD, EUR, GBP, … (full list)
  • display: code=USD, symbol=$, a string
  • digitsInfo: [minIntegerDigits=1].[minFractionDigits=2]-[maxFractionDigits=2] (ex: 3.2-2 -> 000.00)
  • locale: en-US

Parameters digitsInfo and locale work exactly the same for DecimalPipe and PercentPipe.

DecimalPipe API

DecimalPipe

Outside html templates, use formatNumber() instead!

<!-- Signature -->
{{ value_expression | number [ : digitsInfo [ : locale ] ] }}

<!-- Example -->
025.1230 => {{ 25.123 | number:'3.4-4'}}

PercentPipe API

PercentPipe

Use formatPercent() outside templates.

<!-- Signature -->
{{ value_expression | percent [ : digitsInfo [ : locale ] ] }}

<!-- Example -->
26% => {{ 0.259 | percent }}

DatePipe

DatePipe API

Works on a Date, a number (ms since UTC epoch), or an ISO string
Use formatDate() outside templates instead!

All functions like getLocaleDayNames etc have been deprecated in favor of using Intl.

<!-- Signature -->
{{ value_expression | date [ : format [ : timezone [ : locale ] ] ] }}

<!-- Basic examples -->
May 1, 2019 => {{ Date.parse('2019-05-01') | date }}
2019-05-01 23:55:00 => {{ Date.parse('2019-05-01T23:55:00') | date:'yyyy-MM-dd HH:mm:ss' }}

<!-- Full blown example -->
1 mai 2019, 21h00 => {{ Date.parse('2019-05-01T23:55:00') | date:"d MMMM yyyy, HH'h'mm":'-0055':'fr' }}

More options for the format:

  • Escape characters by placing them inside single quotes
  • Week of year: w=7, ww=07
  • Week day: E=Tue, EEEE=Tuesday, EEEEE=T, EEEEEE=Tu
  • Month standalone: LLLL=September
  • Period: a=am/pm
  • Hour 1-12: h=1, hh=01
  • Zone: z=GMT-8, Z=-0800
  • Or named: short, medium, long, full; shortDate, mediumDate (default), longTime, …

Array Pipes

SlicePipe API

SlicePipe (Impure)

<!-- Signature -->
{{ value_expression | slice : start [ : end ] }}

<!-- Examples -->
[ 0, 1 ] => {{ [0, 1, 2, 3, 4] | slice:0:2 | json }}
  
[ 4 ] => {{ [0, 1, 2, 3, 4] | slice:-1 | json }}

[ 0, 1 ] => {{ [0, 1, 2, 3, 4] | slice:-5:2 | json }}

[ 0, 1, 2 ] => {{ [0, 1, 2, 3, 4] | slice:-5:-2 | json }}

KeyValuePipe (Impure)

KeyValuePipe API

Works on Objects and Maps.

<div *ngFor="let item of object | keyvalue:compareFn">
    {{item.key}} => {{item.value}}
</div>

The optional compareFn parameter signature defined in component.ts:

export class AppComponent {
    object = {key1: 1, key2: 2};

    // revert sorting of this.object keys
    // interface KeyValue<K, V>{key: K, Value: V}
    compareFn(a: KeyValue<string, number>, b: KeyValue<string, number>) {
        return b.key.localeCompare(a.key);
    }
}

The output would be:

  • let item of object | keyvalue:compareFn: key2 => 2 and key1 => 1
  • let item of object | keyvalue: key1 => 1 and key2 => 2

AsyncPipe (Impure)

AsyncPipe API

Returns the last emitted value. Unsubscribes automatically in ngOnDestroy.

I typically always use the AsyncPipe instead of .subscribe() in TS.
Do keep track on your DevTools Network Tab though, make sure you are not performing the same http call multiple times when rendering a component!

Time: {{ time$ | async }}

<!-- Capture the value without re-evaluating time$ -->
<div *ngIf="time$ | async as time; else loading">
    {{ time }}
</div>
<ng-template #loading>
    Waiting...
</ng-template>

Where time$ is an Observable or a Promise. Ex:

export class AppComponent {
  time$ = interval(1000).pipe(
    map(() => new Date().toString())
  )
}

i18n Pipes

I18nPluralPipe API

I18nPluralPipe

Display “1 item” but “2 items”, check the structure here.

{{ some_number | i18nPlural: pluralItemsMap }}

const pluralItemsMap = {
  '=0': 'zero items',
  '=1': 'one item',
  'other': '# items',
}

I18nSelectPipe API

I18nSelectPipe

{{ 'F' | i18nSelect: genderMap }}

const genderMap = {
  'M': 'Mr',
  'F': 'Ms',
  'X': 'Mx',
}

Custom Pipes

Create your own pipes for displaying values relevant in your application domain.

Creation

Use ng generate

ng g pipe <name> options

Options:

  • --export=false: Add to module declarations and exports?
  • --flat=true: Write at the top level of the project
  • --lintFix=false
  • --module= and --project=
  • --skipImport=false and --skipTests=false

Example

import { Pipe, PipeTransform } from '@angular/core';

// Usage: 
// 15:30 => {{ someDate | hours }}
// 15h => {{ someDate | hours:true:false  }}

@Pipe({name: 'hours', pure: true})
export class HoursPipe implements PipeTransform {
  transform(value: Date, showHours = true, showMinutes = true): string {
    value = new Date(value);
    if (showMinutes) {
      return value.getHours() + ':' + value.getMinutes();
    }
    return value.getHours() + 'h';
  }
}

Installation

If you didn’t use ng generate.

@NgModule({
  declarations: [AppComponent, CustomPipe],
  exports: [CustomPipe] // Also add to `exports` when declaring in a shared module.
})

Testing

Run tests with ng test.

import { HoursPipe } from './hours.pipe';

describe('HoursPipe', () => {
  const someDate = new Date('2000-01-01T15:30:00');
  let pipe: HoursPipe;

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

  it('shows h:M by default', () => {
    const result = pipe.transform(someDate);
    expect(result).toBe('15:30');
  });

  it('shows 15h with arguments {{ value | hours:true:false }}', () => {
    const result = pipe.transform(someDate, true, false);
    expect(result).toBe('15h');
  });
});

Real World Examples

Some custom examples to get in the right mind what Pipes could be useful for.

Nl2brPipe

Convert newlines in string resources to <br>s.

import { Pipe, PipeTransform } from '@angular/core';

// Usage: <div [innerHTML]="'Line1\nLine2' | nl2br"></div>

// Combine with ngx-translate
// Usage: <div [innerHTML]="'resource.key' | translate | nl2br"></div>

@Pipe({name: 'nl2br'})
export class Nl2brPipe implements PipeTransform {
  transform(value: string): string {
    return value.replace(/\n/g, '<br />');
  }
}

UCFirstPipe

While capitalize (capitalize each word) is bundled with Angular, one to capitalize just the first letter is not.

@Pipe({ name: 'ucfirst' })
export class UcFirstPipe implements PipeTransform {
   transform(text: any): string {
     if (typeof text !== 'string') {
       return text;
     }
    return text.slice(0, 1).toUpperCase() + text.slice(1);
  }
}

BytesPipe

Fancy display of filesizes etc.

// Usage: 1024

@Pipe({ name: 'bytes' })
export class BytesPipe implements PipeTransform {
  private dict: Array<{max: number; type: string}> = [
    { max: 1024, type: 'B' },
    { max: 1048576, type: 'KB' },
    { max: 1073741824, type: 'MB' },
    { max: 1.0995116e12, type: 'GB' },
  ];

  transform(value: number, precision: number = 0): string | number {
    if (isNaN(parseFloat(String(value))) || !isFinite(value)) {
      return NaN;
    }

    const format = this.dict.find(d => value < d.max) || this.dict[this.dict.length - 1];
    const calc = value / (format.max / 1024);
    const num = this.applyPrecision(calc, precision);

    return `${num}${format.type}`;
  }

  applyPrecision(num: number, precision: number) {
    if (precision <= 0) {
      return Math.round(num);
    }

    const tho = 10 ** precision;
    return Math.round(num * tho) / tho;
  }
}

ngx-pipes

Pretty much any generic pipe you can think of already exists. This is one project with such collection.

danrevah/ngx-pipes :

npm install ngx-pipes --save
import { NgPipesModule } from 'ngx-pipes';

// Each pipe is also exposed separately
// ex: import { ReversePipe, LeftTrimPipe } from 'ngx-pipes';

@NgModule({
  imports: [NgPipesModule]
})

String: shorten , slugify , lpad , camelize

Array: unique , tail , flatten , every , …

Other: isGreaterThan , …

angular-pipes

Pretty much the same collection of pipes in this project.

fknop/angular-pipes :

npm install angular-pipes --save
import { NgModule } from '@angular/core';

import {
    NgAggregatePipesModule, NgArrayPipesModule, 
    NgBooleanPipesModule, NgMathPipesModule,
    NgObjectPipesModule, NgStringPipesModule,
} from 'angular-pipes';

// Import all pipes
// import { NgPipesModule } from 'angular-pipes';

@NgModule({
    imports: [
        NgArrayPipesModule,
        NgStringPipesModule,
    ]
})
export class MyApplicationModule {}

String: decodeURI , latinize , repeat , replace , reverseStr , truncate , wrap , stripTags , …

Array: max , min , sum , count , head , …

Number / Math: bytes , degrees , ceil , round , sqrt , …

Other: defaults , …


Stuff that came into being during the making of this post
Other interesting reads
Updates
  • 1 June 2024 : Update to Angular v18: locale configuration, i18n pipes, updated official doc links etc