Kurs Angular · Moduł 10: Zarządzanie stanem
Selektory i efekty - okna i mosty w NgRx
W tej lekcji2
W NgRx cały stan aplikacji leży w jednym wielkim magazynie (store). Rodzą się dwa pytania: jak czytać z niego dokładnie to, czego chcę, i jak łączyć go ze światem zewnętrznym (serwerem)? Odpowiedzią są dwa narzędzia z tej lekcji: selektor to okno na wycinek stanu, a effect to most między akcją a HTTP. Zacznijmy od czytania.
Selektor - okno na wycinek stanu
Selektor to funkcja, która wyciąga ze store'a jeden kawałek. Ma supermoc: przelicza się tylko, gdy jego dane naprawdę się zmienią - reszta czasu zwraca zapamiętany wynik.
import { createFeatureSelector, createSelector } from '@ngrx/store';
export const selectSamuraiState = createFeatureSelector<SamuraiState>('samurai');
export const selectAllSamurai = createSelector(
selectSamuraiState,
state => state.samurai
);
export const selectSamuraiCount = createSelector(
selectAllSamurai,
samurai => samurai.length
);Zauważ, jak selektory się składają: selectAllSamurai czyta listę ze stanu, a selectSamuraiCount buduje się na nim i liczy długość. To jak computed przy sygnałach - selectSamuraiCount przeliczy się dopiero, gdy zmieni się lista, nie przy każdej innej zmianie store'a. Dzięki temu widok nie odświeża się bez potrzeby.
Gdy potrzebujesz selektora z parametrem (np. po id), zwracasz selektor z funkcji:
export const selectSamuraiById = (id: number) => createSelector(
selectAllSamurai,
samurai => samurai.find(s => s.id === id)
);
export const selectSamuraiByClan = (clan: string) => createSelector(
selectAllSamurai,
samurai => samurai.filter(s => s.clan === clan)
);Wzorzec jest ten sam co przy własnych walidatorach: funkcja zewnętrzna przyjmuje parametr (id, clan) i zwraca gotowy selektor. Dzięki temu z jednego selectAllSamurai budujesz dowolne widoki - jednego samuraja, cały klan - bez duplikowania logiki. W komponencie czytasz to przez store.select(selectSamuraiByClan('Iga')).
Effect - most między akcją a serwerem
Selektor tylko czyta. Ale skąd dane trafiają do store'a? Tu wchodzi effect: nasłuchuje akcji (np. "wczytaj samurajów"), robi coś ze światem (żądanie HTTP) i zwraca nową akcję z wynikiem.
loadSamurai$ = createEffect(() =>
this.actions$.pipe(
ofType(loadSamurai), // nasluchuj tej akcji
switchMap(() =>
this.http.get<Samurai[]>('/api/samurai').pipe(
map(samurai => loadSamuraiSuccess({ samurai })), // sukces -> akcja
catchError(error => of(loadSamuraiFailure({ error: error.message })))
)
)
)
);Prześledź most: ofType(loadSamurai) przepuszcza tylko interesującą akcję, switchMap odpala żądanie, a wynik zamieniasz na kolejną akcję - loadSamuraiSuccess albo loadSamuraiFailure. To fundament NgRx: effect nigdy nie zmienia store'a wprost, tylko wysyła akcję, którą reduktor przełoży na nowy stan. Błąd też jest akcją, nie wyjątkiem - dlatego catchError zwraca of(...Failure).
Wybór operatora ma znaczenie - porównaj z wersją dla dodawania:
addSamurai$ = createEffect(() =>
this.actions$.pipe(
ofType(addSamurai),
exhaustMap(({ samurai }) => // ignoruj kolejne, dopoki trwa
this.http.post<Samurai>('/api/samurai', samurai).pipe(
map(saved => addSamuraiSuccess({ samurai: saved })),
catchError(error => of(addSamuraiFailure({ error: error.message })))
)
)
)
);Różnica to switchMap kontra exhaustMap. Przy wczytywaniu switchMap przerywa stare żądanie na rzecz nowego - liczy się tylko najnowszy wynik. Przy zapisie exhaustMap robi odwrotnie: ignoruje kolejne kliknięcia, dopóki trwa poprzednie - dzięki temu podwójne kliknięcie "Zapisz" nie utworzy dwóch samurajów. Zapamiętaj tę parę: switchMap do pobierania, exhaustMap do zapisu.
Pamiętaj z tej lekcji dwie role: selektor to okno, przez które czytasz wycinek stanu (i które samo się przelicza), a effect to most, który zamienia akcję na działanie w świecie i z powrotem w nową akcję. Store zostaje czysty, a cała rozmowa z serwerem płynie przez efekty.
Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Jaka jest główna zaleta createSelector() w NgRx?
2. Do czego służą Effects w NgRx?
To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Edytor kodu
Uzupełnij selektory dla filtrowania samurajów po klanie.
- Układanie w poziomie
Ułóż wywołanie createSelector z selektorem wejściowym i funkcją projekcji (jedna linia, od lewej do prawej):
- Układanie w pionie
Uporządkuj kroki wewnątrz Effectu loadSamurai$ (od nasłuchu akcji do wyniku):
- Klikanie w kolejności
Kliknij elementy w kolejności, aby zbudować początek strumienia w Effekcie nasłuchującym akcji: