Wróć do strony głównej
Angular

Angular Standalone API

Angular w swojej 14 wersji wprowadził do użytku standalone API, a w ramach 15 wersji standalone API zostało oznaczone jako stable (uprzednio znajdowało się w statusie “developer preview”). Zmiana, która odbiła się dużym echem w całym community, będąc jednym z większych powiewów świeżości w naszym frameworku od momentu wprowadzenia IVY. W tym artykule postaramy się dogłębnie przyjrzeć temu, co to dla nas oznacza, jakie korzyści oraz zagrożenia niosą za sobą standalone components i jak potencjalnie mogą one się jeszcze zmienić w przyszłości.

Tworzenie standalone component

Standalone component (co warto zaznaczyć, to tyczy się również dyrektyw i pipe’ów), tworzymy poprzez ustawienie nowo dodanej flagi standalone w dekoratorze komponentu na true.

Tak zadeklarowany komponent może następnie zostać zaimportowany przez inny standalone komponent, moduł lub wykorzystany w obrębie deklaracji routingu. Co ważne taki komponent nie może znaleźć się w deklaracji modułu. Jeżeli spróbujemy to zrobić uzyskamy błąd kompilacji.

Od teraz również w naszym komponencie możemy analogicznie do modułów importować inne standalone komponenty i moduły.

Injectors

Dotychczas w Angularze rozróżnialiśmy dwa typy injectorów – module i element. Aczkolwiek możliwość stworzenia aplikacji angularowej bez żadnego modułu (z wyjątkiem tych pochodzących od samego Angulara), wymusiło na nas poniekąd zmianę nomenklatury, ponieważ nazwa module injector wydaje się poniekąd niezasadna. Tym sposobem, od teraz to, co mieliśmy w zwyczaju nazywać module injectors, przyjęło nazwę environment injectors

Environment injectory możemy skonfigurować w następujący sposób: 

  • @NgModule.providers
  • @Injectable({provideIn: „…”})
  • providers jako parametr funkcji bootstrapAplication (w przypadku “standalone’owej” aplikacji)
  • providers w obrębie deklaracji routingu

O ile dwa pierwsze sposoby nie są tutaj żadną nowością, dwóm ostatnim warto się przyjrzeć bliżej.

bootstrapAplication

Jest to nowa funkcja, którą uzyskaliśmy w celu inicjalizacji aplikacji działającej w oparciu o standalone components. Jako parametr przyjmuja ona ‘root component’ naszej aplikacji, który musi być oznaczony jako standalone, a jako drugi parametr obiekt konfiguracyjny, na ten moment składający się tylko z kolekcji providerów, które mają znaleźć się w naszym root injectorze. Sama funkcja zwraca Promise<ApplicationRef>.

Różnica w inicjalizacji aplikacji opartej o moduł, a component przedstawia się następująco:


 

Z racji tego, że w standalone’owej aplikacji nie posiadamy już modułów, rozwiązywanie zależności Injectora w oparciu o nie, nie jest już możliwe. Providery w obrębie komponentu są rozwiązywane w inny sposób niż miało to miejsce w przypadku modułów (o czym w dalszej części artykułu). W tym celu otrzymaliśmy kolejną funkcję pomocniczą – importProvidersFrom. Funkcja ta służy do zebrania wszystkich providerów spośród wskazanych modułów i standalone komponentów (a właściwie modułów zaimportowanych w obrębie standalone komponentów). Funkcja może zostać użyta tylko w obrębie metody bootstrapApplication lub deklaracji routingu. Nie może zostać wykorzystane w obrębie deklaracji providerów wewnątrz komponentu. Dlaczego dotychczas nie potrzebowaliśmy tego typu funkcji? Ponieważ uprzednio działając w obrębie aplikacji opartej o moduły, importując jakiś moduł angular dodawał do naszego injectora wszystkie jego providery oraz providery modułów, które wskazany moduł importował. W sposób graficzny, przedstawia to poniższy graf. Jest to również reprezentacja tego, jak zadziała funkcja importProvidersFrom na wskazanych modułach. Bazując na naszym przykładzie inicjalizacji aplikacji standalonowej do naszego injectora zostaną dodane m.in HttpClient i Router.

Należy również mieć na uwadze, że funkcja ta może w przyszłości zniknąć lub być znacznie rzadziej wymagana, gdyż Angular dostarczy nam dedykowanych funkcji do konfiguracji poszczególnych modułów, np. tak to może wyglądać w przypadku routingu

Router providers

Dzięki tej zmianie, w obrębie deklaracji naszego routingu, możemy dodawać również providery, które będą dostępne w komponentach pod wskazaną ścieżką oraz jej dzieciach.

Żeby dobrze zrozumieć jak będą działały router providers, pozwolę przedstawić to poprzez analogię do do tego jak obecnie działa environment injector dla lazy loado’wanych modułów. Dla każdego lazy modułu Angular tworzy nowy environment injector, który zawiera kopie naszego root environment injectora oraz wszystkie providery zadeklarowane w modułach znajdujących się w lazy loadowanym drzewie modułów. (Analogicznie dla naszego obrazka z poprzedniej sekcji, tylko zamiast root environment injector byłby tam nasz environment injector tworzony w ramach lazy loadowania). 

Obecnie w ten sam sposób działają router providers, aczkolwiek obecnie do tworzenia environment injectora dla określonej grupy komponentów pod danym routem nie musi on już działać w oparciu o Api lazy loadowania. Dzieje się to dla każdej grupy providerów w obrębie deklaracji routingu. Taki environment injector będzie zawierał kopie root environment injectora oraz providery zadeklarowane w providers dla danej ścieżki. 

Dla wielu z nas pracujących z jakimkolwiek state managementem, to właśnie routing staniem się docelowym miejscem inicjalizacji kolejnego kawałka naszego stanu

Dlaczego nie CartComponent moglibyśmy zapytać? Dlatego, że właśnie sposób w jaki działają providery componentu nie uległ zmianie i wszystko co dodamy w obrębie komponentu do naszych providerów, zostanie dodane do Element Injectora związanego z instancją naszego komponentu, a nie zostanie przekazane do góry jak to miało miejsce w przypadku modułu co przedstawiała nasza grafika. State aplikacji jest globalny, po jego rejestracji, możemy chcieć uzyskać dostęp do zarejestrowanego kawałka stanu w zupełnie innej części aplikacji, dlatego też musi on być skonfigurowany w sposób globalny. 

Standalone Injector

Wydawałoby się, że dotychczasowe zmiany już sporo nam namieszały, a to jeszcze nie koniec. W niektórych przypadkach Angular jest zmuszony stworzyć nowy “standalone injector”. Wszystko to, żeby połączyć ze sobą dwa światy – modularny oraz standalone. Najczęstszym przypadkiem kiedy taki injector będzie tworzony jest następująca sytuacja: 

Standalonowy komponent (CarouselComponent) wykorzystuje inny komponent (CarouselCardComponent), który nie jest standalone, a więc jesteśmy zmuszeni do zaimportowania modułu w, którym on się znajduje (CarouselCardModule). Dodatkowo wykorzystywany komponent korzysta z serwisu (CarouselCardService), który również jest providowany na poziomie wspomnianego modułu.

W momencie gdy Angular tworzy nasz standalone’owy komponent, musi mieć pewność, że zapewnia wszystkie niezbędne providery do funkcjonowania tego komponentu oraz jego zależności, również tych opartych o moduły. I właśnie w tym celu zostanie utworzony “standalone injector” jako dziecko “environment injectora”, pod który podlega tworzony komponent.

Routing i lazy loading

API routera również uległo uproszczeniu, w celu zapewnienia wsparcia dla standalone komponentów. Obecnie nie potrzebujemy już modułów do realizacji lazy loadingu.

Lazy loading pojedynczego komponentu

Obecnie możemy lazy loadować dowolny standalone komponent przy wykorzystaniu metody loadComponent:

Lazy loading grupy komponentów

Obecnie loadChildren pozwala na załadowanie całego zestawu child route’ów bez konieczności tworzenia dodatkowego lazy loadowanego modułu, który deklarowałby je poprzez wykorzystanie RouterModule.forChild

Podsumowanie

Jak widać skala usprawnień jest duża, aczkolwiek jest to bardzo duży krok w kontekście rozwoju Angulara i wszelkie przyszłe zmiany wydają się jeszcze bardziej ekscytujące.

Dajcie znać w komentarzu jak pracuje wam się z nowym API.

O autorze

Mateusz Stefańczyk

Angular Team Leader w House of Angular. Zapalony redaktor angular.love. W portalu od wielu lat. Miłośnik konsol maści wszelakiej i najróżniejszych indyczków 🙂 Fan piłki nożnej zarówno tej prawdziwej, jak i wirtualnej. Każdego weekendu siada głęboko w fotelu, zapina pasy i rozkoszuje się europejskimi rozgrywkami.

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.

9 komentarzy

  1. Marek

    Dobry artykuł 🙂 Brakuje adnotacji jak autor widzi aplikację przy użyciu bootstrapApplication bez ngZone. Z tego co widziałem powstało issue na githubie nt. braku możliwości jego użycia, bądź co bądź warto o tym poinformować w newsie 🙂

  2. Mariusz

    Do sekcji Lazy loading pojedynczego komponentu dodałbym, że istnieje możliwość skrócenia zapisu poprzez dodanie słowa kluczowego default do komponentu, a następnie:
    ts export const ROUTES: Route[] = [ {path: 'admin', loadComponent: () => import('./admin/panel.component')}, // ... ];

  3. Bart

    Przydałby się jakiś artykuł jak skonfigurować bootstrapAplication w kontekście SSR i angularbuniversalis. Obecnie po wielu probowach niestety nie udało mi się tego zrobić tak żeby aplikacja działala. Musiałem pozostać przy jednym appmodule głównym

  4. Wiktor

    I tak przepisanie istniejącego codebase na nowe opcje jest ekstremalnie trudne.
    Używam teraz standalone komponentów zamiast malutkich modułów dla reużywalnych elementów mojego UI, tutaj działa to super <3

Dodaj komentarz

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