Angular Pipes

Angular Pipes

posted in javascript on

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

This post covers:

  • All Builtin Angular pipes (json, async, string, array)
  • 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 } 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 €
  }
}

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

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: LOCALE_ID, useValue: 'fr'}],
})
export class AppModule { }

CurrencyPipe

CurrencyPipe API

Default currency is $, this can as of yet not be set globally. Consider using a Money class 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

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

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

PercentPipe API

PercentPipe

<!-- 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

<!-- 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.

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$ = new Observable<string>(observer => {
    setInterval(() => observer.next(new Date().toString()), 1000);
  });
}

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