27 maj 2021
5 min

NgRx – tips & tricks

Na naszym blogu pojawił się już kiedyś wpis o wskazówkach do NgRx autorstwa Tomasza Nastałego, który mocno zachęcam przeczytać. Po długiej przerwie nadeszła pora na następną porcję tips & tricks – zapraszam!

1. withLatestFrom -> concatLatestFrom 

Z wpisu, który zalinkowałem we wstępie wiesz już, że gdy potrzebujesz skorzystać w efekcie z wartości znajdującej się w state, dobrym rozwiązaniem jest użycie operatora withLatestFrom. Niewiele osób jednak wie, że użycie tego operatora bez skorzystania z flattening operatora (switchMap, concatMap, mergeMap, exhaustMap) ma skutek uboczny. Operator withLatestFrom subskrybuje się, nawet jeśli główny observable w efekcie nie wyemituje wartości. Jeżeli potrzebujesz stworzyć efekt bez używania flattening operatora, zamiast withLatestFrom użyj concatLatestFrom. Dla przykładu:

Tutaj nastąpi subskrypcja selektora fromBooks.getCollectionBookIds niezależnie od tego czy akcja AddBookSuccess zostanie zdispatchowana:

@Injectable()
export class CollectionEffects {
  addBookToCollectionSuccess$ = createEffect(
    () =>
      this.actions$.pipe(
        ofType(CollectionApiActions.addBookSuccess),
        withLatestFrom(
        this.store.select(fromBooks.getCollectionBookIds)),
        tap(([action, bookCollection]) => {

...

Tutaj natomiast selektor fromBooks.getCollectionBookIds zasubskrybuje się tylko wtedy, gdy akcja AddBookSuccess zostanie zdispatchowana:

@Injectable()
export class CollectionEffects {
  addBookToCollectionSuccess$ = createEffect(
    () =>
      this.actions$.pipe(
        ofType(CollectionApiActions.addBookSuccess),
        concatLatestFrom(action => this.store.select(fromBooks.getCollectionBookIds)),
        tap(([action, bookCollection]) => {
...

2. Nx DataPersistence kluczem do dobrych efektów

Nx DataPersistence dostarcza zestaw pomocniczych funkcji, które umożliwiają deweloperowi zarządzanie stanem w Angularze z uwzględnieniem strategii synchronizacji i obsługi błędu. Są to:

  • Optimistic update – w pierwszej kolejności aktualizuje dane po stronie klienta, dopiero później pozwala je synchronizować z danymi uzyskanymi z backendu. Na wypadek niepowodzenia aktualizacji danych po stronie serwera dostarcza nam handler undoAction, który pozwala na wycofanie wprowadzonych zmian po stronie klienta.
  • Pessimistic update – odwrotność powyższego. Najpierw aktualizuje dane po stronie api, a po pomyślnej operacji aktualizuje dane po stronie klienta. W razie niepowodzenia operacji po stronie serwera, nie ma potrzeby cofania wywołanych zmian, jednak na czas oczekiwania na odpowiedź serwera warto zadbać o poinformowanie użytkownika o trwającej operacji (np. przy użyciu spinnera).
  • Fetch – pobieranie danych. Ważnym elementem jest możliwość przekazania id pobieranego obiektu do efektu, dzięki czemu w razie wywoływania tej samej akcji dla różnych obiektów, zapytania wykonają się równolegle.
  • Navigate – pozwala na sprawdzenie czy aktywowana ścieżka zawiera przekazany w parametrze komponent i jeśli tak jest, wykonanie zadanego polecenia.

Nx DataPersistance bardzo ułatwia zarządzanie stanem z NgRx, przez co zachęcam do przeczytania dokumentacji i wdrożenia go do swojego projektu.

3. Nawigacja z użyciem NgRx

W miarę rozrastania się naszej aplikacji zarządzanie nawigacją może się skomplikować. Do ułatwienia debugowania routingu Twojej aplikacji użyj router-store’a. Od teraz store będzie dispatchował szereg akcji przy każdej uruchomionej nawigacji, co w połączeniu z Redux Devtoolsami pozwala na prześledzenie krok po kroku tego się dzieje w nawigacji.

Dodatkowo, jeżeli w projekcie korzystasz z @ngrx/entity, możesz użyć selektorów przygotowanych pod korzystanie z router-store. Dają one m.in. możliwość wybierania danych ze store w oparciu o ścieżkę url, bez konieczności pobierania informacji o route w komponencie.

4. Między stanem, a komponentem – fasada

Aby ułatwić organizację komponentu korzystającego ze store i poprawić czytelność kodu możesz użyć fasady. Fasada jest odpowiedzialna za utworzenie dodatkowej warstwy między komponentem, a stanem aplikacji. Możesz ją rozumieć jak API, które jest udostępnione dla komponentów przez store. Nie będę rozpisywać się o tym jak utworzyć fasadę, gdyż jest już na ten temat świetny artykuł od Thomasa Burlesona. Od siebie dodam, że po implementacji pierwszej fasady nie chciałem i nie wróciłem już nigdy do bezpośredniego używania store w komponentach.

5. Runtime checks

Jeżeli chcesz mieć pewność, że Twoje funkcjonalności oparte o NgRx są zgodne z jego kluczowymi konceptami, skorzystaj z runtime checks – opcji do konfiguracji store’a. Runtime checks umożliwiają sprawdzanie działania naszego kodu i w razie potrzeby informowanie o błędzie w konsoli. Jest to bardzo pomocna rzecz w trakcie developmentu. Jeżeli chcesz poznać wszystkie opcje konfiguracyjne, odsyłam Cię do świetnie napisanej dokumentacji.

6. Wyłuskiwanie payloadu w efekcie

Jeżeli do akcji przekazujesz obiekt i chcesz wydobyć jego wartość w efekcie, możesz użyć operatora pluck

Gdybyśmy chcieli użyć takiej akcji:

export const removeBook = createAction(
'[Book Collection] Remove Book',
props<{ bookId }>()
);

to efekt prawdopodobnie wyglądałby tak:

removeBook$ = createEffect(() => this.actions$.pipe(
ofType(removeBook),
switchMap((action) =>
  this.booksService.removeBook(action.bookId).pipe(
    map(() => removeBookSuccess({bookId: action.bookId}))
  )
)
));

Z użyciem operatora pluck pozbywamy się konieczności wielokrotnego odnoszenia się do wartości bookId z action:

removeBook$ = createEffect(() => this.actions$.pipe(
ofType(removeBook),
pluck('bookId'),
switchMap((bookId) =>
  this.booksService.removeBook(bookId).pipe(
    map(() => removeBookSuccess({bookId}))
  )
)
));

Na dzisiaj koniec!

Dajcie znać w komentarzach czy dzięki temu wpisowi dowiedzieliście się czegoś nowego i czy chcielibyście więcej tego rodzaju contentu na blogu!

Podziel się artykułem

Zapisz się na nasz newsletter

Dołącz do community Angular.love i bądź na bieżąco z trendami.