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:
- pure pipe, czyli odmiana domyślna, w założeniu nie posiada wewnętrznego stanu, dlatego Angular stara się wykorzystać tę samą instancję w jak największej liczbie miejsc, w ramach jednego widoku. Oznacza to oszczędność pamięci oraz, co ważne w naszym przypadku, czasu potrzebnego na stworzenie kolejnych instancji
- impure pipe to z kolei rodzaj, który możemy nadać dla własnego Pipe'a poprzez użycie flagi { pure: false } w dekoratorze @Pipe. Odmiana ta może posiadać wewnętrzny stan a Angular tworzy nową instancję dla każdego wystąpienia Pipe'a w widoku. Jest to opcja znacznie bardziej pamięcio- i czasochłonna, dlatego powinniśmy używać jej z rozmysłem.
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!