Wróć do strony głównej
NGXS

Wszystko, co musisz wiedzieć, aby rozpocząć pracę z NGXS

Założę się, że już słyszałeś / czytałeś o zarządzaniu stanem. Jeśli nie, założę się ponownie, że chcesz, aby twoje aplikacje były łatwiejsze do utrzymania / rozszerzenia. Oczywiście, posiadanie zarządzania stanem nie czyni aplikacji automatycznie rozciągliwą, ale prowadzi nas do posiadania Separation of Concerns, aby to osiągnąć. W tym artykule wyjaśnię czym jest State Management i jak używać go w swoich aplikacjach Angularowych przy użyciu NGXS.

Co to jest stan?

Zanim zanurkujemy głębiej i zaczniemy kodować, najpierw zobaczmy podstawy i zrozumiemy, czym jest „state”. 

Załóżmy, że mamy komponent Toggle, w którym użytkownik może włączyć lub wyłączyć. Ten komponent ma dwa stany. Stan „On” i stan „Off”. Powiedziawszy to, stan jest reprezentacją danego czasu.

Jako użytkownicy już korzystamy ze stanu w różnych obszarach, takich jak Nawigacja, Komponenty, Aplikacja, itp. Niektóre przykłady to:

  • Nawigacja
    Stan nawigacji mówi, na której stronie w danym momencie jesteśmy w aplikacji
  • Komponenty
    Mówiąc o komponencie Toggle, stanem może być On lub Off
  • Aplikacja
    Jest to kategoria, którą zobaczysz w wielu wpisach na blogu. Dla ułatwienia stan Application opisałbym jako Globalny stan aplikacji. Ale czekaj, co mamy na myśli mówiąc Globalny stan? Cóż, Globalny stan to aplikacje lub tokeny użytkowników, dane serwera itp.

Być może zastanawiasz się, jak wygląda stan. 

Jeśli myślimy o component state, to jest to informacja, która jest przechowywana w samym komponencie.

W powyższym kodzie isToggled odpowiada za utrzymanie toggle state.

A jak to się ma do global state? To nie jest nic innego jak object notation.

W powyższym obiekcie widzimy dwa zagnieżdżone obiekty. Użytkownika oraz todoItems. Te informacje są globalnie dostępne z poziomu aplikacji. Należy tutaj zauważyć, że te zagnieżdżone obiekty są również nazywane state slices.

Dlaczego State Management?

Jak dotąd stan wydaje się być bardzo łatwy do zarządzania, prawda? Wszystko co mamy to obiekt z kilkoma plastrami (zagnieżdżonymi obiektami), do których możemy mieć bezpośredni dostęp. Dlaczego więc musimy poświęcić więcej czasu na naukę korzystania z biblioteki zarządzania stanem?

Pomyśl o następującym; Szanse są takie, że możesz chcieć dodać elementy w tablicy TodoItems z jednego komponentu i zobaczyć reakcję na innym komponencie. Możesz pomyśleć, że to też nie jest trudne. Zgadzam się!!! To może być po prostu kilka BehaviourSubjects/Observables, które przechowują wszystkie informacje. Możemy również potrzebować upewnić się, że kiedy usuwamy elementy, subskrybenci na tym Observables otrzymują powiadomienia. Upewnij się, że obejmujesz wszystkie przypadki, w których usuwasz elementy. Być może będziesz musiał mieć funkcję mapy, która zbiera wszystkie elementy isDone=true i wszystkie elementy iisDone=false. A co jeśli chcemy użyć tych tablic z wielu miejsc? Czy będziemy uruchamiać tę funkcję mapy n* razy? Nie byłoby to aż tak szkodliwe, ponieważ jest to tylko funkcja mapowa i jest stosunkowo szybka. Jeśli jednak mamy bardziej zaawansowane i czasochłonne operacje, musielibyśmy mieć wzór memomizacji.

Powyższe to niektóre przypadki, które musimy wziąć pod uwagę dla stanu globalnego. Musimy również powtórzyć wszystkie te przypadki, jeszcze raz i jeszcze raz, aby zastosować je do każdego plasterka stanu (zagnieżdżonego obiektu). Jeśli będziemy dalej rozwijać i rozszerzać ten kod, to gratulacje, udało nam się zbudować własną bibliotekę zarządzania stanami ?

Tworzenie biblioteki zarządzania stanami, jest jak ponowne wynalezienie koła, prawda? Przyjrzyjmy się zatem NGXS.

NGXS Core Concepts

NGXS obejmuje wzór CQRS, podobny do tego, co robią NgRX i Redux. W większości przypadków CQRS łączy wzór Event Sourcing, gdzie w skrócie używa zdarzeń, które dyktują stan danych. Niektóre przykłady zdarzeń mogą być AddTodoItem lub RemoveTodoItem. Tak więc, kiedy wykonujemy akcję, zdarzenie jest wysyłane i to zdarzenie mutuje stan w Store. Mutacja stanu odbywa się za pomocą funkcji; możesz myśleć o tej funkcji jako o obsłudze zdarzenia.

Aby to zobrazować, zobaczmy szybki przykład. 

Powiedzmy, że mamy następujący domyślny State w Store.

Jeśli chcemy dodać element do stanu, powinniśmy wysłać zdarzenie AddTodoItem, a także mieć handler zdarzenia, który zajmuje się tym zdarzeniem. Jeśli więc wyślemy zdarzenie AddTodoItem({id: 1, name: 'a todo item’, isDone: false}), stan stanie się

I oczywiście musimy mieć sposób na odczytanie danych ze Store. Coś w stylu:

Zobaczmy teraz, jak powyższe jest tłumaczone przy użyciu terminów NGXS

  • Akcje
    Możesz myśleć o akcjach jako o zdarzeniach. W powyższym przykładzie todo, akcjami mogą być AddTodoItem, RemoveTodoItem, MarkTodoAsRead, MarkTodoAsUnread. Jak widać, Akcje opisują interoperacyjność aplikacji.
  • Store
    Store jest kontenerem, w którym znajdują się stany. W powyższym przykładzie wspomnieliśmy o plasterkach stanu. Aplikacja może mieć wiele stanów, a te stany są przechowywane w Store. Zapewnia on również sposób na odczytywanie danych ze stanów, mutowanie stanów, itp. Tak więc, Store jest sercem biblioteki State Management.
  • Stan
    Stan jest kontenerem, który reprezentuje schemat danego wycinka stanu, obsługuje również działania tego stanu. Podczas gdy Store jest sercem biblioteki zarządzania stanami, stan jest sercem konkretnego wycinka stanu.
  • Kwerendy/Selektory
    Kwerendy to mechanizm, za pomocą którego pobieramy dane ze stanu. Należy pamiętać, że operacje Read i Write są w NGXS inne, co jest zgodne z wzorcem CQRS.

Poniższy obrazek definiuje przepływ z wykorzystaniem semantyki NGXS.

  1. Komponent wysyła akcję
  2. Akcja mutuje stan w Store
  3. Komponent wybiera dane z Store używając selektorów

NGXS Example

Tak!!! Czas kodowania!!! 

Stworzymy kolejną aplikację TODO, która wygląda jak na poniższym filmiku.

1) Instalacja

2) Zarejestruj moduł NGXS w app.module.ts

Powyższa tablica imports posiada NgxsModule.forRoot([]) gdzie musimy dodać stan, który będziemy tworzyć.

3) Utworzenie stanu i zdefiniowanie modelu

Stwórzmy model każdego elementu TODO. Widząc film możemy stwierdzić, że musimy mieć Tytuł, Status (czy jest aktywny czy nie) i być może id jako nasz unikalny identyfikator.

To jest model każdego elementu, ale musimy mieć również typ dla samego stanu. 

Oznacza to, że wszystkie moje pozycje TODO będą przechowywane we właściwości items tego stanu. 

Do tej pory udało nam się tylko podłączyć moduł i stworzyć modele. Zobaczmy teraz, jak stworzyć stan.

Stan jest po prostu klasą, ale zauważ, że używamy dekoratora  @State , w którym definiujemy nazwę stanu i domyślne. 

  • Nazwa jest przydatna do odróżnienia jednego wycinka stanu od drugiego.
  • Defaulty są przydatne do zdefiniowania domyślnego stanu podczas inicjalizacji aplikacji.

4) Utwórz akcje

Obserwując film widzimy, że potrzebujemy akcji do tworzenia pozycji TODO oraz do aktualizacji statusu pozycji TODO (true/false).

Każda akcja to klasa, w której definiujemy type jako unikalny identyfikator oraz constructor, w którym deklarujemy argumenty. Pamiętasz, że powiedzieliśmy, że o akcji można myśleć jak o zdarzeniu? Ponieważ zdarzenia mają argumenty zdarzeń, używamy konstruktora, aby je wyrazić.

5) Wysyłanie działań

Po przesłaniu formularza wywołujemy metodę add(), w której wykorzystujemy Store do wysłania akcji. Komponent jest tutaj odpowiedzialny za wysłanie akcji z pewnymi argumentami, a nie jest odpowiedzialny za mutowanie stanu elementów TODO (Single Responsibility Principle). Oczekujemy, że elementy TODO staną się length + 1, ale powinno to nastąpić w todo.state.ts.

6) Obsługa działań

Stworzyliśmy metodę addTodo i dodatkowo udekorowaliśmy tę metodę dekoratorem @Action. W ten sposób łączymy akcję z metodą. W skrócie, metoda ta będzie wywoływana przez Store, gdy akcja AddTodo zostanie zadysponowana. Store wywoła metodę przekazując dwa argumenty. 

  1. State context (ctx), gdzie możemy uzyskać stan, zmienić stan i wiele innych.
  2. Akcja, gdzie posiada argumenty zdarzenia. Możemy również opisać akcję jako payload

Należy pamiętać, że kiedy obsługujemy akcję, w większości przypadków zachowujemy stan taki jaki jest i zmieniamy tylko to, co ta akcja ma zrobić. Dlatego właśnie używamy operatorów rozprzestrzeniania.

Zrobiliśmy już duży postęp i w tym momencie spodziewamy się, że do naszego stanu trafi kilka elementów. Wybierzmy te elementy za pomocą selektorów!

7) Utwórz selektory

Zgodnie z filmem, wymaganiami są wyświetlenie elementów na liście, wyświetlenie liczby wykonanych elementów, a także wyświetlenie elementów aktywnych. Z tego powodu stworzymy trzy selektory.

Mamy trzy statyczne metody, gdzie każda z nich jest ozdobiona dekoratorem @Selector. Kiedy Store wywołuje metodę selector podaje jako argumenty model stanu, który mamy zdefiniowany w tablicy.

W selektorze items zwracamy tylko state.items.

W pozostałych dwóch selektorach zwracamy state.items, gdzie isActive jest albo true albo false.

Ponadto zwrócony obiekt jest wycinkiem stanu i nie możemy mutować stanu za pomocą tego obiektu. Ponieważ NGXS obejmuje wzór CQRS, odczyt i zapis są różnymi operacjami i jako takie używamy selektorów, aby tylko pobrać dane ze stanu.

Proszę zauważyć, że selektory w NGXS mają możliwość użycia wzorca memoization.

8) Użyj selektorów w komponencie

Ponieważ mamy trzy selektory, potrzebujemy również trzech właściwości klasy. Każda z nich jest dekorowana za pomocą dekoratora @Select dostarczającego statyczną metodę selektora. 

activeItems$ i doneItems$ wydają się nie mieć pełnego przepływu, ponieważ nie oznaczyliśmy jeszcze elementów jako wykonanych. W artykule poniżej znajdziesz link do kodu. Przed sprawdzeniem kodu wypróbuj go, a następnie porównaj swoje rozwiązanie z linkiem.

NGXS i Lazy Modules

Na początku artykułu rozmawialiśmy o plastrach stanu. Dla przypomnienia, powiedzieliśmy, że stan jest obiektem, w którym każdy zagnieżdżony obiekt jest plasterkiem stanu. Do tej pory utworzyliśmy plasterek todo, który posiada wszystkie TODO we właściwości items

A co w przypadku, gdy potrzebujemy stworzyć więcej plasterków, a ponadto, co w przypadku, gdy potrzebuję użyć plasterka stanu w lazily loaded module?

Logika i kod są dokładnie takie same, a jedyną różnicą jest sposób, w jaki rejestrujemy moduł stanu

Zauważ, że zamiast używać NgxsModule.forRoot() używamy metody forFeature().

Wnioski

Po pierwsze i najważniejsze, bardzo się cieszę, że przeczytałeś mój artykuł. Nie spodziewałem się, że będzie on tak długi, ale mam nadzieję, że dowiedziałeś się jak łatwo jest używać NGXS, a także jak ważne jest posiadanie biblioteki zarządzania stanami w swojej aplikacji.

Jest jeszcze wiele innych rzeczy, które możemy zrobić z NGXS i gorąco zachęcam Cię do sprawdzenia oficjalnej dokumentacji https://www.ngxs.io/

O autorze

Fanis Prodromou

Jestem full-stack web developerem z pasją do Angulara i NodeJs. Mieszkam w Atenach-Grecji i pracowałem w wielu dużych firmach. Podczas moich 14 lat kodowania zdobyłem ogromne doświadczenie w zakresie jakości kodu, architektury aplikacji oraz ich wydajności.

Zdając sobie sprawę z tego, jak szybko rozwija się informatyka i aspekty techniczne, staram się być na bieżąco, uczestnicząc w konferencjach i meetupach, studiując i próbując nowych technologii. Uwielbiam dzielić się swoją wiedzą i pomagać innym programistom.

„Sharing is Caring”

Uczę Angulara w firmach korporacyjnych poprzez instytut Code.Hub, piszę artykuły i tworzę filmy na YouTube.

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.

3 komentarzy

  1. Maciej

    Świetny artykuł 🙂 Łatwy w podążaniu zanim i jasno pokazujący wartość jaką prezenetuje ngsx oraz Store ogólnie. Super także jest to, że była sekcja kodowania dla chcących rozwijać się w angularze.

  2. Maciej

    Świetny artykuł 🙂 Stosunkowo łatwy w przyswojeniu oraz jasno wytłumaczony. Fajnie, że była sekcja praktyczna, która umożliwia podjęcie próby samodzielnego zastosowania zdobytej wiedzy wraz z dostępnymi odpowiedziami jak to powinno naprawdę wyglądać.

  3. Maciej

    Dwie sprawy tylko: NgxsModule.forRoot([]) potrzebuje jeszcze w nawiasach kwadratowych TodoState aby poprawnie załadował się store. Ale może jest to zawarte w całości kodu, który miał zostać załączony w linku, ale chyba link do całości się gdzieś zapodział 🙂
    P.S. Fajnie by było jakby jakiś snackbar wyskoczył po dodaniu komentarza, ponieważ user nie widzi czy coś się zglitchowało czy komentarz się poprawnie dodał.

Dodaj komentarz

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