Wróć do strony głównej
NgRx

@ngrx/component

W jednym z ostatnich wpisów na naszym blogu (NgRx – nie tylko store) opisywaliśmy biblioteki oferowane przez NgRx. Wśród nich znalazło się wiele dobrze znanych pozycji, jednak pojawiły się także nowości. Jedną z nich jest @ngrx/component – biblioteka dająca narzędzia wspomagające implementację reaktywnych komponentów. W tym artykule poświęcimy jej nieco więcej uwagi – dowiesz się, co oferują jej obecne elementy – dyrektywa *ngrxLet oraz pipe ngrxPush. Odpowiemy też sobie na bardzo ważne pytanie, czy powinniśmy stosować je produkcyjnie. Zatem zaczynajmy!

Instalacja

W celu zainstalowania biblioteki w terminalu wystarczy uruchomić komendę:
npm install @ngrx/component
lub
ng add @ngrx/component@latest

Obydwa wspomniane elementy biblioteki są eksportowane przez ReactiveComponentModule, który musimy zaimportować w module naszej aplikacji i możemy startować.

ngrxPush

NgrxPush znane też jako pushPipe jest odpowiednikiem dobrze znanego wszystkim AsyncPipe. Użycie ngrxPush w komponencie wygląda niemal identycznie:

Spójrz na przykładową aplikację korzystającą w jednym z komponentów z async, a w drugim z ngrxPush. Kod komponentów jest identyczny, różnią się one jedynie na poziomie template:

 

 

Na pierwszy rzut oka obydwa wymienione wyżej pipe’y działają wręcz identycznie. Gdzie więc tkwi różnica? Okazuje się, że ngrxPush przynosi korekty na poziomie sterowania systemem detekcji zmian.

W standardowej aplikacji napisanej w Angularze, obydwa pipe’y działają w następujący sposób: kiedy w strumieniu danych pojawi się nowa wartość, aktualizują wartość zmiennej przekazywanej do widoku i używają metody markForCheck, aby poinformować system wykrywania zmian o potrzebie ponownego wyrenderowania widoku (o mechanizmach detekcji więcej możesz przeczytać tutaj). Użycie metody markForCheck samo w sobie nie spowoduje jednak uruchomienia mechanizmu detekcji zmian i ponownego wyrenderowania komponentu, a jedynie oznaczy komponent i wszystkich jego przodków jako przeznaczone do sprawdzenia. W jaki więc sposób mimo to otrzymujemy oczekiwany rezultat, w którym nowe dane są renderowane? Odpowiedzialny jest za to NgZone, który uzupełnia wspomniany wyżej mechanizm i to dzięki niemu w widoku pojawiają się nasze nowe dane (artykuł o NgZone znajdziesz tutaj).

ngrxPush i Zoneless mode

W idealnym scenariuszu widok powinien reagować na samą zmianę wartości danych, bez wspomagania ze strony NgZone czy potrzeby manualnego wywoływania detekcji zmian. Są też klasy aplikacji, których deweloperzy mogą zwyczajnie nie chcieć korzystać z NgZone np. z powodów wydajnościowych. Istnieje też szereg innych potencjalnych powodów, jednak jest to osobny problem, wykraczający poza temat tego artykułu.
Co się więc stanie, gdy wyłączymy zone w naszej aplikacji? Sprawdźmy postępując zgodnie z instrukcją w dokumentacji: https://angular.io/guide/zone#noopzone

 

Jak widać, zgodnie z oczekiwaniami, komponent korzystający z asyncPipe nie odświeża teraz wartości strumienia w widoku. Natomiast drugi komponent, który korzysta z ngrxPush, działa dalej poprawnie mimo braku NgZone. Dzieje się tak dlatego, że pipe ten w przypadku braku zone wykorzystuje metodę detectChanges, która jest odpowiedzialna za bezpośrednie uruchomienie mechanizmu detekcji zmian.
Czy takie rozwiązanie ma jakieś wady? Niestety tak. W przypadku, gdy w widoku używamy bezpośrednio tego samego Observable w kilku miejscach z ngrxPush, detekcja zmian zostanie wywołana tyle razy, ile jest tych użyć. Mimo, iż zwykle jest to bardzo szybki proces, takie zachowanie stanowi dość duży minus. Czy możemy jednak temu jakoś zapobiec? Odpowiedź znowu brzmi “tak” i wcale nie chodzi tu o wykorzystanie *ngIf. Pomoże nam w tym drugi bohater dzisiejszego artykułu – *ngrxLet.

 

*ngrxLet

Zanim przejdziemy do samej dyrektywy *ngrxLet, odpowiedz sobie wcześniej na jedno pytanie: ile razy zdarzyło Ci się spotkać z kodem wykorzystującym *ngIf w podobny sposób?

 

Zapewne wiele razy i w zasadzie nie ma w tym nic dziwnego. Angular nie dostarcza żadnej alternatywy pomocnej w takich przypadkach, a deweloperzy naturalnie szukają prostego rozwiązania zastępczego. Niestety ma ono spory minus jeśli chodzi o takie zastosowanie. W przypadku otrzymania w strumieniu wartości rozumianej jako fałszywa, oznaczony tą dyrektywą element zostaje usunięty z drzewa DOM. W tym kontekście jest to często niepożądany efekt.

Właśnie tutaj z pomocą może przyjść nam *ngrxLet – to, co przede wszystkim odróżnia tę dyrektywę od *ngIf to:
– element zawsze pozostaje w drzewie DOM, niezależnie od wartości otrzymanej ze strumienia danych,
– dyrektywa ta nie potrzebuje dodatkowego użycia asyncPipe czy ngrxPush, aby “odpakować” dane ze strumienia, więc możemy podać jej bezpośrednio Observable.

Dodatkowo *ngrxLet, wspiera inne zdarzenia strumienia, co oznacza, że łatwo możemy obsłużyć powiadomienia o błędach lub o zakończeniu emisji. Podobnie jak przy ngrxPush tutaj także mamy możliwość pracy w środowisku Zoneless.
Poniżej nasza przykładowa aplikacja z wyeliminowanymi powtórzeniami ngrxPush na rzecz *ngrxLet. W drugim komponencie dla porównania async i *ngIf, co powoduje ukrycie całych elementów ul przy wartości początkowej w strumieniu będącej null:

 

Czy używać produkcyjnie?

W tym miejscu warto zaznaczyć, że jeszcze do niedawna biblioteka opierała się na pewnych funkcjach związanych z detekcją zmian (https://github.com/ngrx/platform/blob/9.2.0/modules/component/src/core/cd-aware/get-change-detection-handling.ts#L4-L5), które pojawiły się wraz z Ivy i które nie należą do publicznego API Angulara. Zapewne głównie z tego względu początkowo można ją było pobrać jedynie z repozytorium NgRx na Githubie. Od wersji 10 twórcy zdecydowali się natomiast użyć API istniejącego dotychczas ChangeDetectora i udostępnić ją w rejestrze npm.

Odwiedzając dokumentację @ngrx/component od razu możemy jednak zauważyć następującą informację:

This package is still experimental and may change during development.

W związku z tym faktem postanowiłem zgłębić ten temat nieco bardziej. Niestety okazuje się, że issue zawierające roadmapę dla biblioteki (https://github.com/ngrx/platform/issues/2441) zostało zamknięte przez głównego kontrubutora. Śledząc historię biblioteki na GitHubie można zauważyć, że spora część pomysłów i kodu została odrzucona. Zapewne było to bezpośrednim powodem przerwania kontrybucji.
Chcąc mieć jednak stuprocentową pewność co do statusu i dalszych losów biblioteki, zasięgnąłem informacji u samego źródła, czyli członka core teamu NgRx, Alexa Okrushko. Streszczając odpowiedź, zespół nie daje gwarancji na dalszy rozwój biblioteki – być może w przyszłości będzie ona wycofana lub nawet usunięta. W związku z tym Alex zachęca do jej sprawdzenia, ale równocześnie zaleca, by raczej nie używać jej w aplikacjach produkcyjnych. Na pocieszenie można dodać, że Alex wspomniał też o innym, alternatywnym pomyśle NgRx na aplikacje Zoneless. Na razie jest on jednak w bardzo wczesnym stadium, jego rozwój ma raczej niski priorytet i szczegóły nie są ujawniane.

Podsumowanie

Biblioteka @ngrx/component wnosi sporo świeżości do rodziny NgRx dostarczając zupełnie inne narzędzia niż te, do których zostaliśmy przyzwyczajeni, czyli związane z zarządzaniem stanem. Pomimo, że jej przyszłość jest wątpliwa, nadal warto zapoznać się z jej założeniami i problemami, jakie stara się rozwiązać. Daje to szerszą perspektywę na kierunki rozwoju aplikacji i bibliotek Angularowych.

O autorze

Krzysztof Skorupka

Angular Team Leader w House of Angular. Dowodzi członkami zespołów pomagając im rozwinąć się w tworzeniu oprogramowania na najwyższym poziomie, a jednocześnie sam tworzy aplikacje internetowe Angular. Interesuje się także analizą biznesową i architekturą aplikacji webowych dostosowanych do potrzeb klienta.

Zapisz się do naszego newslettera. Bądź na bieżąco z najnowszymi trendami, poradami, meetupami i stań się częścią społeczności Angulara w Polsce. Rynek pracy docenia członków społeczności.

Jeden komentarz

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *