Nitrooos

Myśli programisty

HyphenatePipe - uzupełnienie do ostatniego wpisu

W jednym z moich niedawnych wpisów (Hyphenation - automatyczne dzielenie wyrazów na sylaby) poruszyłem problem zgodnego z gramatyką dzielenia długich wyrazów na sylaby w aplikacji. Cel ten został osiągnięty poprzez zaimplementowanie własnego pipe'a, czyli klasy HyphenatePipe. Uznałem, że wpis ten wymaga uzupełnienia, ponieważ podana w nim implementacja zawiera pewne niedopowiedzenie. Spokojnie, nie jest to krytyczna podatność, ale okazuje się, że w pewnych okolicznościach może napsuć nam nerwów ;) O co chodzi? Zapraszam do przeczytania krótkiego uzupełnienia!

TLDR;
Operacja tworzenie Pipe'a ze wspomnianego wpisu (HyphenatePipe) jest czaso- i pamięciochłonna. Przy intensywnym używaniu tego narzędzia Angular będzie (potencjalnie) wielokrotnie potwarzał tę kosztowną operację, zupełnie niepotrzebnie. Potrzebna jest modyfikacja przedstawionej implementacji tak, aby tworzenie instancji HyphenatePipe było znacznie mniej kosztowne.

W jaki sposób w Angularze działa narzędzie Pipe?

Za dokumentacją, w Angularze rozróżnia się dwa podstawowe rodzaje Pipe'ów:

Definicja problemu

Pipe, który opisałem (HyphenatePipe) co prawda należy do tego pierwszego rodzaju, jednak bardzo łatwo przeoczyć pewien fakt. Używając Pipe'a w ramach elementów potomnych (nawet jeśli są częścią tej samej strony aplikacji i są typu pure) zostaną utworzone osobne instancje Pipe'a. Każda z nich odpowiadać będzie pojedynczemu użyciu w widoku. Jakie ma to znaczenie? Aby to zrozumieć należy zastanowić się...

Czym właściwie zajmuje się konstruktor klasy HyphenatePipe?

Tworzy on nową instancję obiektu klasy Hypher (pole o nazwie hyphenator). Operacja ta wymaga zaimportowania odpowiedniego wzorca językowego do dzielenia wyrazów, a także parsowania go. W przypadku opisywanego języka (niemiecki) wzorzec ten jest bardzo duży (ok 70kB danych), w ogólności mogą mieć one od kilku do kilkudziesięciu kB. W połączeniu z tworzeniem instancji klasy HyphenatePipe dla każdego użycia konstrukcji " ... | hyphenate" w kodzie, opóźnia to ładowanie naszej aplikacji :/

Niestety, do tego stopnia, że problem ten okazał się być głównym czynnikiem wydłużającym czas ładowania aplikacji. W moim przypadku Pipe tworzony był 8 razy, przy czym za każdym razem wykonywana była operacja importu wzorca językowego. Dokładnie o 7 razy za dużo <facepalm>.

Rozwiązanie problemu z klasą HyphenatePipe

Rozwiązaniem jest oczywiście wykonanie importu wzorca językowego tylko 1 raz. Możemy to osiągnąć poprzez wydzielenie operacji tworzenia obiektu hyphenator klasy Hypher do dedykowanego serwisu. W naszym przypadku będzie on nazywał się HyphenationPatternsService. Implementacja jest prosta, spokojnie można przedstawić ją w jednym fragmencie kodu:

import { Injectable } from '@angular/core';
import * as Hypher from 'hypher';
import * as german from 'hyphenation.de';

@Injectable()
export class HyphenationPatternsService {
  private hyphenator: Hypher = null;

  constructor() {
    this.hyphenator = new Hypher(german);
  }

  public hyphenate(word: string) {
    return this.hyphenator.hyphenate(word);
  }
}

Następnie możemy wstrzyknąć ten serwis jako zależność do klasy HyphenatePipe:

constructor(private hyphenationService: HyphenationPatternsService) { }

Od teraz tworzenie nowych instancji HyphenatePipe jest bardzo szybkie i nie rzutuje na czasie ładowania aplikacji. Wzorzec językowy dla dzielenia wyrazu na sylaby jest ładowany tylko 1 raz. Gotowe!