Tworzenie własnych potoków

Poza używaniem standardowych potoków, Angular pozwala tworzyć własne. Każda niestandardowa potoku musi:

  • Posiadać dekorator @Pipe z metadanymi potoku, wśród których jest własność name. Ta wartość zostanie wykorzystana do wywołania potoku. Musi to być poprawny identyfikator JavaScript.
  • Implementować metodę transformacji interfejsu PipeTransform. Ta metoda pobiera z potoku wartość oraz zmienną liczbę argumentów dowolnego typu i zwracają wartość przekształconą (piped).
import { Pipe, PipeTransform } from '@angular/core';

const FILE_SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const FILE_SIZE_UNITS_LONG = ['Bytes', 'Kilobytes', 'Megabytes', 
          'Gigabytes', 'Pettabytes', 'Exabytes', 'Zettabytes', 'Yottabytes'];

@Pipe({
  name: 'formatFileSize'
})
export class FormatFileSizePipe implements PipeTransform {
  transform(sizeInBytes: number, longForm: boolean): string {
    const units = longForm
      ? FILE_SIZE_UNITS_LONG
      : FILE_SIZE_UNITS;

    let power = Math.round(Math.log(sizeInBytes)/Math.log(1024));
    power = Math.min(power, units.length - 1);

    const size = sizeInBytes/Math.pow(1024, power); // size in new units
    const formattedSize = Math.round(size * 100)/100; // keep up to 2 decimals
    const unit = units[power];

    return `${formattedSize} ${unit}`;
  }
}

Każdy parametr rozdzielany dwukropkami w szablonie odwzorowuje jeden argument metody w tej samej kolejności.

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

@Component({
  selector: 'app-root',
  template: `
    <div>
      <p *ngFor="let f of fileSizes">{{ f | formatFileSize }}</p>
      <p>{{ largeFileSize | formatFileSize:true }}</p>
    </div>`
})
export class AppComponent {
  fileSizes = [10, 100, 1000, 10000, 100000, 10000000, 10000000000];
  largeFileSize = Math.pow(10, 15)
}

Zobacz przykład [0038]

Tworzenie potoków ze stanem

Potoki są domyślnie bezstanowe. Aby zadeklarować, że potok jest stanowy, ustawiamy właściwośćpuredekoratora@Pipena wartość false. To ustawienie mówi systemowi wykrywania zmian Angular, aby sprawdzał wynik potoku w każdym cyklu, niezależnie od tego, czy wejście potoku uległo zmianie, czy nie (zmiany mogą dotyczyć stanu).

import { Pipe, PipeTransform, ChangeDetectorRef } from '@angular/core';
import { Observable, interval } from 'rxjs';
import { take } from 'rxjs/operators';
// zob. https://angular.io/guide/rx-library

// naive implementation assumes small number increments
@Pipe({
  name: 'animateNumber',
  pure: false
})
export class AnimateNumberPipe implements PipeTransform {
  private currentNumber: number = null; // intermediary number
  private targetNumber: number = null;

  transform(targetNumber: number): string {
    if (targetNumber !== this.targetNumber) {
      this.currentNumber = this.targetNumber || targetNumber;

      this.targetNumber = targetNumber;

      const difference = this.targetNumber - this.currentNumber

      const msecCounter = interval(100).pipe( take(difference) );
      msecCounter.subscribe(n => {
          this.currentNumber++;
        });
    }
    return String(this.currentNumber);

  }
}

Zobacz przykład[037]