Angular course ยท Module 10: State Management
Selectors and Effects - windows and bridges in NgRx
In this lesson2
In NgRx the entire application state lives in one big store. Two questions follow immediately: how do you read exactly the slice you need, and how do you connect the store to the outside world (the server)? This lesson answers both with two tools: a selector is a window onto a slice of state, and an effect is a bridge between an action and an HTTP request. Let us start with reading.
Selector - a window onto a slice of state
A selector is a function that pulls one piece out of the store. It has a superpower called memoization: it recomputes only when its own dependencies change - the rest of the time it simply hands back the remembered result.
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
);Read it from the top. createFeatureSelector<SamuraiState>('samurai') selects an entire branch of the state - the feature registered under the name samurai. That is all it does: it does not register the reducer in the module, it does not define a loading action, and it is not an effect. Every createSelector call below it follows the same shape: input selectors first, the projector function last. selectAllSamurai reads the list out of the feature, and selectSamuraiCount composes on top of it and returns the length; the very same pattern gives you selectLoading from state.loading.
That composition is where memoization pays off. Much like computed with signals, selectSamuraiCount recomputes only when the list changes, not on every other store update - so the view never re-renders for nothing. And memoization really is the whole benefit: a selector does not save anything to localStorage, does not sort your results, and performs no runtime type validation.
When you need a selector with a parameter (by id, for example), you return a selector from a function:
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)
);The pattern is the same one you met with custom validators: the outer function takes the parameter (id, clan) and returns a ready selector. Notice what stays fixed - the input selector is always selectAllSamurai, and only the projection function changes. From that single source you build any view you like, one samurai or a whole clan, with no duplicated logic. In a component you read it with store.select(selectSamuraiByClan('Iga')).
Effect - a bridge between an action and the server
A selector only reads. So how does the data get into the store in the first place? This is where the effect comes in: it listens for an action (say, "load the samurai"), does something with the outside world - an HTTP request - and returns a new action carrying the result. Effects are meant for exactly that kind of side effect; they are not for UI animations, not for styling components, and not for form validation. Each one lives in an @Injectable() class where the stream of every dispatched action arrives as private actions$ = inject(Actions), which is why the chain always starts with this.actions$.pipe(...).
loadSamurai$ = createEffect(() =>
this.actions$.pipe(
ofType(loadSamurai), // listen for this action
switchMap(() =>
this.http.get<Samurai[]>('/api/samurai').pipe(
map(samurai => loadSamuraiSuccess({ samurai })), // success -> action
catchError(error => of(loadSamuraiFailure({ error: error.message })))
)
)
)
);Walk across the bridge in order: ofType(loadSamurai) listens for the action and lets only that one through, switchMap calls the API, map turns the response into the success action loadSamuraiSuccess, and catchError turns a failure into the error action loadSamuraiFailure. That sequence - listen for the action, call the API, success action, error action - is the skeleton of every loading effect you will write. It also shows the foundation of NgRx: an effect never changes the store directly, it only emits an action that the reducer translates into new state. An error is an action too, not a thrown exception, which is why catchError returns of(...Failure).
The choice of operator matters just as much - compare it with the version used for adding:
addSamurai$ = createEffect(() =>
this.actions$.pipe(
ofType(addSamurai),
exhaustMap(({ samurai }) => // ignore new ones while one is running
this.http.post<Samurai>('/api/samurai', samurai).pipe(
map(saved => addSamuraiSuccess({ samurai: saved })),
catchError(error => of(addSamuraiFailure({ error: error.message })))
)
)
)
);The difference is switchMap versus exhaustMap. When loading, switchMap cancels the old request in favor of the newest one - only the freshest result counts. When saving, exhaustMap does the opposite: it ignores further clicks while the previous request is still in flight, so a double click on "Save" can never create two samurai. Remember that pair: switchMap for fetching, exhaustMap for saving.
Keep two roles from this lesson. A selector is the window you read a slice of state through, and it recomputes only when its dependencies change. An effect is the bridge that turns an action into work in the outside world and back into a new action. The store stays pure, and the whole conversation with the server flows through effects.
Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. What is the main advantage of createSelector() in NgRx?
2. What are Effects used for in NgRx?
These are 2 of 3 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Code editor
Complete the selectors for filtering samurai by clan.
- Horizontal ordering
Arrange the createSelector call with an input selector and a projection function (one line, left to right):
- Vertical ordering
Order the steps inside the loadSamurai$ Effect (from listening for the action to the result):
- Click in order
Click the elements in order to build the start of the stream in an Effect that listens for an action: