What is NgRx?
NgRx is a state management library for Angular, built on the Redux pattern and RxJS. All shared state lives in one store that you never change directly. The only way to change it is to dispatch an action that a pure reducer handles.
35 questions - Intermediate to advanced
NgRx questions are really questions about the Redux pattern used in Angular. Interviewers want to see that you understand one-way data flow, that reducers must be pure, that side effects live in effects, and just as important, that you know when NgRx is too much.
Last updated:
NgRx is a state management library for Angular, built on the Redux pattern and RxJS. All shared state lives in one store that you never change directly. The only way to change it is to dispatch an action that a pure reducer handles.
In a big app the same data is needed in many unrelated components. Passing it around, or copying it into several services, ends with screens that disagree with each other. NgRx gives one source of truth, one clear way to update it, and a full history of what happened.
Store holds the state. Actions describe events. Reducers are pure functions that make the next state. Selectors read parts of the state. Effects do side effects such as HTTP and then dispatch more actions.
A component dispatches an action, the reducer makes new state from the old state and that action, selectors push the changed part back into the component, and any effect listening to that action can do async work and dispatch a follow-up action.
One single source of truth. State is read-only and changes only by dispatching actions. Changes are made by pure reducer functions with no side effects.
When the app is small. When the state is used by one component only. When the data is really a cache of the server. Adding NgRx to a small app gives a lot of extra code and no benefit. A service with a BehaviorSubject or a signal is often enough.
Both share state. NgRx adds structure and tools: one strict update path, time-travel devtools, effects for async flows, memoised selectors and easy tests. A service is simpler, but every team member can change state in their own way.
An action is a plain object with a unique type string and optional data. It says something happened. Actions should describe events, not commands: "Login Page: Login Submitted", not "Set User".
export const loadUsers = createAction("[Users Page] Load Users");
export const loadUsersSuccess = createAction(
"[Users API] Load Users Success",
props<{ users: User[] }>()
);
Because the source tells you where the action came from when you read the devtools log, so you debug a flow much faster. It also stops people reusing one action from many places, which makes reducers hard to predict.
A reducer is a pure function that takes the current state and an action and returns the next state. It must not change the state in place, must not call APIs, and must not use random values or the current time. The same input must always give the same output.
export const usersReducer = createReducer(
initialState,
on(loadUsers, (state) => ({ ...state, loading: true })),
on(loadUsersSuccess, (state, { users }) => ({ ...state, users, loading: false })),
on(loadUsersFailure, (state, { error }) => ({ ...state, error, loading: false }))
);
Being pure makes it easy to test and lets devtools replay actions. Never changing state in place lets Angular OnPush and NgRx selectors spot a change by comparing references, which is fast. If you change state in place, the UI simply does not update.
Yes. Every reducer sees every action. So one "Logout" action can reset several feature slices at the same time. That is a correct use of a shared action.
A meta-reducer wraps the root reducer and sees every action before or after it. It is used for logging, for clearing the whole state on logout, and for saving and restoring state from localStorage.
Not every click needs an action. Dispatch only when shared state really changes, or when a side effect must start. Local UI state, like whether an accordion is open, should stay inside the component.
A selector is a function that reads a part of the state. createSelector builds a memoised selector. It runs again only when its inputs change, and otherwise returns the same reference. That stops OnPush components from rendering for no reason.
export const selectUsersState = createFeatureSelector<UsersState>("users");
export const selectAllUsers = createSelector(
selectUsersState,
(state) => state.users
);
export const selectActiveUsers = createSelector(
selectAllUsers,
(users) => users.filter((u) => u.active)
);
The selector remembers its last inputs and its last result. If the inputs are the same references, it returns the saved result and does not run the function again. That is why you should work out derived data in selectors, not in the component.
Because it copies data that can go out of date, and it makes the state bigger. Store the least you can and work out the rest in selectors.
It returns a selector for one whole feature slice by its registered key. Feature selectors then do not need to know where that slice sits in the global state tree.
Return a function from the selector using props, or better, use a factory function that creates a selector per id. Be careful: a factory selector loses its memoisation if you create it inside the template.
export const selectUserById = (id: string) =>
createSelector(selectAllUsers, (users) => users.find((u) => u.id === id));
An effect listens to the stream of actions, does the side effect - usually an HTTP call - and then dispatches a new action with the result. It keeps reducers pure by taking all async work out of them.
loadUsers$ = createEffect(() =>
this.actions$.pipe(
ofType(loadUsers),
switchMap(() =>
this.api.getUsers().pipe(
map((users) => loadUsersSuccess({ users })),
catchError((error) => of(loadUsersFailure({ error: error.message })))
)
)
)
);
Because an error on the outer actions stream kills the effect for good. It will never react to that action again. Catching inside the inner Observable means only that one request fails and the effect keeps listening.
switchMap for reads where only the newest matters, like a search. concatMap for writes where order matters. exhaustMap for a login or a save, so a double click does not fire twice. mergeMap when the calls are independent and can run together.
It is an effect that only does something and does not produce a new action: navigating with the router, showing a toast, writing to localStorage. Without that option, NgRx would try to dispatch undefined and throw an error.
redirect$ = createEffect(
() => this.actions$.pipe(ofType(loginSuccess), tap(() => this.router.navigate(["/dashboard"]))),
{ dispatch: false }
);
Use concatLatestFrom or withLatestFrom on a selector. concatLatestFrom is better, because it reads the state only when the action actually arrives.
Give it a fake actions stream with provideMockActions, stub the API service, send the action in, and check the action that comes out. An effect is just Observable in and Observable out, so it is easy to test.
An effect listens to action A and dispatches action A again, directly or through another effect. The devtools log then shows the same action over and over. Fix it by making the output action different from the input action.
NgRx Entity is a helper for storing collections. It keeps items in a normalised shape: an ids array plus an entities map. It also gives ready-made addOne, upsertMany, updateOne and removeOne functions and selectors, so you do not write array logic by hand.
const adapter = createEntityAdapter<User>();
export const initialState = adapter.getInitialState({ loading: false });
on(loadUsersSuccess, (state, { users }) => adapter.setAll(users, { ...state, loading: false }));
Because finding or updating one item through the entities map is O(1), instead of scanning an array. And the same item is not copied into several places, so the copies cannot go out of sync.
createFeature is a newer API. It puts the reducer and the slice name together and then creates the feature selector plus one selector per state property for you. It removes a lot of repeated selector code.
ComponentStore is a small local store for one component or one feature. It has state, updaters, selectors and effects, but it lives and dies with the component and never enters the global store. It is a good middle step when the global store feels too heavy.
It is a newer store built on Angular signals instead of Observables. You read state through signals at once, computed values take the place of selectors, and it needs far less code than the classic store, while keeping the same one-way flow.
It is a browser extension that shows every action, the state before and after it, and lets you jump back to any point in time. It turns "the screen is wrong" into "this action made the wrong state", which is far faster to debug.
Keep loading and error fields in the feature slice. Set loading to true on the request action, and to false on both success and failure. Store the message on failure. Then expose both as selectors so the template can show a spinner or an error.
Use createActionGroup and createFeature. Use Entity for collections. Keep local UI state out of the store. Think about ComponentStore or the Signal Store for smaller features. Do not put everything in the global store just because it is there.
The pattern is the same. NgRx is built around RxJS and Angular dependency injection, uses effects for async work, and createSelector for memoisation. Redux Toolkit uses thunks or listeners, and Immer for updates. The ideas move straight across.
CrackInterviewAI listens to the live interview and gives a structured answer on screen for coding, system design, HR and project questions. Download for Windows.