Wróć do strony głównej
Angular

Progresywny Angular cz. 2

Nadszedł nareszcie czas na przedstawienie procesu integracji istniejącej aplikacji z Service Workerami. W obrębie tego artykułu przedstawimy trzy różne dostępne podejścia jeśli chodzi o przekształcanie projektu w PWA.

Na potrzeby artykułu stworzyliśmy prostą aplikację mającą za zadanie wyświetlanie grafiki pobranej z API wystawionego przez nasz serwer. Sama grafika zwracana przez API zmienia się co 5 sekund.

Nasze SW natomiast będą realizowały strategię stale-while-revalidate, co oznacza, że wyświetlana grafika będzie pobierana z cache’a, natomiast w tle przeprowadzana będzie synchronizacja.

W momencie, gdy podczas uruchomienia aplikacji nowa grafika będzie dostępna, powinien zostać wyświetlony SnackBar informujący użytkownika o dostępnej aktualizacji. Gdy użytkownik wyrazi chęć pobrania nowych danych, aplikacja zostanie przeładowana.

Całość prezentuje się następująco:

Poglądowa aplikacja.

Jeśli chodzi o samą aplikację, gotowy kod źródłowy dostępny jest w naszym repozytorium. Zachęcamy do pobrania i eksperymentowania, a po więcej informacji odsyłamy do README.md.

 

  1. Angular PWA
    1.1. Angular.json
    1.2. App.module
    1.3. Index.html
    1.4. Ikony
    1.5. Service Worker Service
    1.6. Repository Service
  2. Serwer
  3. Recipes
    3.1. Service Worker
  4. Angular Service Worker
  5. WorkBox
  6. Recipes vs Angular Service Worker vs WorkBox
    6.1. Recipes
    6.2. Angular Service Worker
    6.3. WorkBox
  7. Podsumowanie
  8. Przydatne linki

Angular PWA

Aby przekształcić naszą aplikację w PWA musimy dodać paczkę @angular/pwa. W tym celu wykonujemy polecenie:

Uwaga! Istotne aby nie używać w tym celu Nx CLI.

Powyższe polecenie spowoduje wygenerowanie Service Workera mającego za zadanie cache’owanie zasobów naszej aplikacji. Szczegółowy proces jest zawarty w dokumentacji Angulara.

Po dodaniu wspomnianej paczki nasza aplikacja została dodatkowo spatchowana w kilku miejscach.

Angular.json

W pliku konfiguracyjnym zostały wprowadzone zmiany powodujące budowanie i kopiowanie predefiniowanego Angular Service Workera wraz z naszą aplikacją.

Zmiany w Angular.json.

Jak widać na powyższym porównaniu, do zasobów naszej aplikacji został dołączony manifest, włączona została flaga serviceWorker oraz ustawiona została ścieżka do ngswConfigPath, zawierającego konfigurację dla budowanego Service Workera.

Wspomniany Web App Manifest zawiera podstawowe informacje o naszej aplikacji oraz odpowiada za to w jaki sposób będzie prezentowana użytkownikowi po jej instalacji. Po więcej informacji odsyłamy do dokumentacji MDN lub do artykułu web.dev omawiającego pokrótce większość dostępnych opcji.

App.module

Moduł naszej aplikacji dodatkowo został wzbogacony o ServiceWorkerModule, który odpowiada za zarejestrowanie samego Service Workera.

Zmiany w app.module.ts.

Index.html

Wspomniany wyżej Web App Manifest musi zostać jeszcze uwzględniony w naszym index.html, wówczas przeglądarka po otwarciu strony z naszą aplikacją będzie wiedziała, że jest to PWA. Oprócz tego domyślnie zostaje ustawiony theme-color, odpowiadający za kolorystykę baneru przeglądarki w trakcie odwiedzin.

Zmiany w index.html.

Ikony

Oprócz wspomnianych powyżej zmian do aplikacji dołączane są ikony, które będą wykorzystywane w PWA (ikona samej aplikacji, ikona na SplashScreenie).

Niestety ng serve nie wspiera PWA, dlatego aby przetestować poprawność integracji naszej aplikacji musimy skorzystać z osobnego serwera HTTP. Możemy w tym celu użyć http-server (więcej szczegółów znajdziemy w dokumentacji).

Dodatkowym ograniczeniem jest konieczność instalacji paczki @angular/pwa tylko w obrębie modułu samej aplikacji (konieczne jest zaimportowanie ServiceWorkerModule w app.module), nie można jej zainstalować w obrębie innego modułu lub biblioteki.

Service Worker Service

Aby zapewnić komunikację między SW oraz naszą aplikacją, musimy nasłuchiwać na zdarzenie message definiując właściwość ServiceWorkerContainer.onmessage. Na tę potrzebę zdefiniowany został ServiceWorkerService, mający metodę rozpoczynającą nasłuchiwanie.

Repository Service

W celu wydzielenia logiki pobierającej zdalne zasoby utworzony został osobny serwis RepositoryService. Na tą chwilę zawiera on pojedynczą metodę pobierającą grafikę z naszego serwera, który utworzymy w kolejnym rozdziale.

Serwer

W ramach naszej integracji konieczne jest również utworzenie serwera wystawiającego API do pobierania grafiki zmieniającej się co 5 sekund.

W tym celu musimy mieć zainstalowany plugin @nrwl/nest, z którego skorzystamy w celu wygenerowania serwerowej aplikacji NestJS. Wykonujemy wówczas polecenia:

 

oraz

Implementacja sprowadza się do wystawienia endpointa, po zapytaniu którego zwracany jest jeden z dostępnych obrazków. Kod serwisu odpowiedzialnego za ustalanie grafiki sprowadza się do następującej metody:

Metoda odpowiedzialna za zmianę serwowanej grafiki.

Kluczowym punktem jest ustawienie ETagu w nagłówku odpowiedzi na unikalną wartość odpowiadającą aktualnej grafice, na podstawie którego nasza aplikacja wykryje zmianę grafiki.

Dodatkowym krokiem, który musimy jeszcze wykonać jest zezwolenie na dostęp do wspomnianego wyżej nagłówka po stronie klienta. Zmiany należy wprowadzić w pliku main.ts.

Konfiguracja serwera dająca dostęp do wybranego nagłówka po stronie klienta.

Recipes

Bazując na konfiguracji którą przeprowadziliśmy w poprzednich rozdziałach, możemy w końcu zabrać się za implementację Service Workera.

Na początek zaczniemy bez korzystania z gotowych bibliotek a jedynie z Service Worker API. W celu realizacji strategii stale-while-revalidate możemy skorzystać z gotowego przepisu.

Service Worker

Implementacja sprowadza się do kilku kluczowych punktów oraz metod. Podczas instalacji SW do pamięci podręcznej wrzucane są stałe pliki. W przypadku naszej aplikacji sprowadza się to do pliku index.html, ikony aplikacji oraz manifestu.

PreCaching zasobów aplikacji.

Najważniejsza logika natomiast znajduje się w metodzie wywoływanej na zdarzeniu fetch, czyli za każdym razem gdy jakikolwiek zasób jest pobierany.

Metoda wywoływana gdy zasób aplikacji jest requestowany.

Jak widać powyżej, w momencie gdy aplikacja wysyła żądanie po jakiś zasób, jest on odczytywany z pamięci podręcznej. W międzyczasie jest on pobierany, zapisywany w cache’u oraz wysyłana jest wiadomość do aplikacji o dostępności nowych danych.

Należy tutaj zwrócić uwagę, że mechanizm ten jest wykorzystywany dopiero w momencie, gdy SW zostaje aktywowany. Aktywacja natomiast następuje w zależności od registrationStrategy ustawionej podczas rejestracji SW.

W przypadku registerImmediately, SW jest rejestrowany natychmiastowo i przy użyciu Clients.claim() możemy od razu przejąć kontrolę nad stroną, zapisując do pamięci podręcznej dane pochodzące z pierwszych requestów.

Gdy jednak nie mamy możliwości przechwytywania już pierwszych żądań, aktywacja SW nastąpi po przeładowaniu aplikacji, dopiero wówczas żądania będą cache’owane.

Aby złagodzić powyższy problem, można ewentualnie pokusić się o cache’owanie większej ilości plików podczas instalacji, nie rozwiąże to natomiast kwestii z grafikami dostarczanymi przez nasz serwer. Tutaj z kolei możemy zastosować mechanizm fallbacku, wyświetlając jakiś placeholder zamiast aktualnej grafiki. To jakie ostateczne rozwiązanie zastosujemy zależy od charakteru aplikacji oraz wymaganych upodobań.

Ostatnią rzeczą jest usunięcie właściwości dotyczących Angular Service Worker z pliku angular.json tak aby nie kopiować nie wykorzystywanego NGSW oraz zminimalizować ostateczną wielkość aplikacji.

Zmiany w angular.json mówiące o tym, że nie chcemy korzystać z NGSW.

Angular Service Worker

W naszej aplikacji wykorzystanie predefiniowanego Angular Service Workera sprowadza się do odpowiedniej konfiguracji w pliku ngsw-config.json.

Najistotniejszym punktem jest ustawienie dataGroups definiującego mechanizm cache’owania zasobów pobieranych z określonych adresów. W przypadku naszej aplikacji, najbardziej interesuje nas cache’owanie grafiki pobieranej z serwera przy użyciu strategii stale-while-revalidate, a można to zrealizować ustawiając odpowiednie wartości pól strategy oraz timeout.

Cacheowanie obrazków przy użyciu strategii stale-while-revalidate.

W tym momencie pozostaje nam jedynie implementacja komunikacji z SW do naszej aplikacji o dostępnych nowych danych.

Realizujemy to poprzez utworzenie nowego Service Workera, który nasłuchuje na zdarzenie fetch i w przypadku gdy dotyczy to interesujących nas danych, wysyła wiadomość do klienta. Aby nie utracić przy okazji funkcjonalności NGSW, musimy skorzystać z WorkerGlobalScope.importScripts() o którym była mowa w poprzednim artykule, w rozdziale “Wiele Service Workerów”.

Pomijając metodę refresh(), całość wygląda następująco:

 

Rozszerzenie Angular Service Worker.

WorkBox

W przypadku definicji Service Workera przy użyciu modułów WorkBox, nasza implementacja sprowadza się do skorzystania z jednej z metod udostępnianych przez paczkę workbox-recipes.

Stosując podobne rozwiązanie co w przypadku Angular Service Worker, rozszerzamy naszą implementację o wysyłanie wiadomości do klienta o dostępnej nowej grafice.

Implementacja SW przy użyciu WorkBoxa.

Należy również tutaj zwrócić uwagę, że podczas parsowania naszego SW pobierana jest biblioteka workbox-sw z CDN, udostępniająca globalny obiekt workbox, z którego to wyciągamy interesujące nas moduły. Używane przez nas moduły są automatycznie cache’owane.

Dodatkowo, analogicznie jak w implementacji przy użyciu Przepisu, usuwamy właściwości dotyczące Angular Service Workera z pliku angular.json.

Recipes vs Angular Service Worker vs WorkBox

Po przekształcaniu naszej aplikacji w PWA oraz integracji z różnego rodzaju Service Workerami, mamy mniej więcej pogląd na to w jaki sposób możemy to zrobić oraz czym różnią się przedstawione podejścia. Spróbujmy zatem podsumować w kilku słowach zebraną wiedzę oraz informacje.

Recipes

Surowa implementacja Service Workera przy użyciu Service Worker API zawiera stosunkowo dużo boilerplate kodu w porównaniu do pozostałych. Problemy sprawia również wersjonowanie cache’a (trzeba ręcznie zmieniać nazwę wraz ze zmianą wersji), a implementacja niektórych funkcjonalności staje się niewspółmiernie zawiła.

Jest to natomiast doskonałe miejsce do rozpoczęcia swojej przygody z SW, gdyż tylko w ten sposób w pełni zrozumiemy co dzieje się “pod spodem”. Ponadto mamy pełną kontrolę nad zachowaniem SW więc możemy implementować swoje własne, niestandardowe rozwiązania.

Należy jednak liczyć się z tym, że niekoniecznie będą one bezpieczne, w tym przypadku bezpieczeństwo aplikacji leży całkowicie po stronie programisty. Dodatkowo próg wejścia jest stosunkowo wysoki w porównaniu do alternatywnych rozwiązań.

Angular Service Worker

Jeśli chodzi o Angular Service Worker, świetne sprawdza się w przypadku aplikacji Angularowych, które chcą skorzystać tylko z podstawowych funkcjonalności PWA takich jak cache’owanie czy Push Notifications. Proces instalacji jest prosty a implementacja zazwyczaj sprowadza się do uzupełnienia kilku właściwości w pliku konfiguracyjnym.

Kłopoty zaczynają się gdy chcemy wprowadzić jakąś naszą własną logikę, wówczas musimy rozszerzać bazową implementację.

Niekoniecznie również sposób w jaki pewne funkcjonalności są rozwiązane w Angular Service Worker będą odpowiadały naszym potrzebom, wówczas nawet rozszerzanie może być niewystarczające. W takim wypadku możemy skorzystać z WorkBoxa.

WorkBox

Korzystając z WorkBoxa pozbywamy się znacznej części boilerplate kodu. Dodatkowo mamy dużą kontrolę nad zachowaniem Service Workera, możemy dowolnie konfigurować indywidualne moduły ze sobą, realizując różnego rodzaju strategie, a nawet definiować własne strategie czy też pluginy.

Jeżeli w wymaganiach naszej aplikacji znajdują się niestandardowe funkcjonalności, jest to zdecydowanie najlepsza propozycja. Dodatkowo inżynierowie z Google’a postarali się również o doskonałą dokumentację wraz z faktycznymi przykładami użycia.

Podsumowanie

Korzystając z PWA z pewnością poprawimy User Experience naszej aplikacji. Istnieje kilka dostępnych sposobów na jej przekształcenie w PWA, a główne z nich przedstawiliśmy Wam w ramach naszego artykułu.

Każde z omawianych podejść ma swoje lepsze i gorsze strony i to które z nich ostatecznie wybierzemy zależy w dużej mierze od stawianych wymagań aplikacji.

Zbierając do kupy omówioną wiedzę przedstawiamy krótkie graficzne zestawienie omawianych podejść w wybranych kategoriach.

Kompleksowe porównanie wybranych aspektów implementacji Service Workerów na różne sposoby.

Mamy nadzieję, że artykuł rzucił trochę światła na ten ciągle ewoluujący i rozległy świat Progressive Web Apps i że również wynieśliście z naszego artykułu coś nowego i wartościowego.

Przydatne linki

  1. https://developers.google.com/web/tools/workbox/modules
  2. https://angular.io/guide/service-worker-getting-started
  3. https://serviceworke.rs/
  4. https://pwa-fundamentals.nl/chapters/service-workers.html
  5. https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers
  6. https://web.dev/two-way-communication-guide/
  7. https://jakearchibald.com/2014/offline-cookbook
  8. https://stackoverflow.com/questions/45257602/sharing-fetch-handler-logic-defined-across-multiple-service-workers
  9. https://developers.google.com/web/fundamentals/primers/service-workers
  10. https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
  11. https://wicg.github.io/background-sync/spec/
  12. https://whatwebcando.today/

O autorze

Marcin Leśniczek

Wiecznie głodny wiedzy z pasją dla aplikacji mobilnych oraz hybrydowych. Zawsze otwarty na nowe pomysły i technologie, szukający dziury w całym. Po godzinach entuzjasta nauki i astronomii.

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.

Dodaj komentarz

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