skip to content

NgRx

NgRx brings the Redux pattern to Angular — actions, reducers, memoized selectors, RxJS effects — plus the signal-based SignalStore. Interviewers expect it once a question leaves component state.

on this pageshow

explore

questions

page 1 of 2

In NgRx, why does a flight-search page's search request belong in an effect rather than in the reducer or the component?

level: juniorimportance: must knowfreq 64%

answer

  1. what a reducer is allowed to do
  2. state in, state out, synchronously
  3. the component only declares intent
  4. Actions stream fires after reducers
  5. the response comes back as an action

basics

~20 s

An NgRx reducer must be a pure, synchronous function from state and action to the next state, so it cannot wait for an API. An effect listens to dispatched actions, runs the request and dispatches a success or failure action back.

solid answer

~40 s

A reducer receives the current state and an action and must return the next state synchronously, with no side effects; a request started there would answer after the reducer had already returned, and replaying actions in the DevTools would fire real calls. The component could call the service itself, but then every screen that searches repeats the loading and error handling, and the store never records what happened. An effect is the third place: it subscribes to the `Actions` stream, which emits each action *after* the reducers have handled it, filters with `ofType(FlightSearchActions.searchRequested)`, calls the flight API and maps the response to `searchSucceeded` or `searchFailed`. The reducers then store that result like any other action, so the component only dispatches intent and reads state through selectors.

code

ts · 25 lines
ts
import { inject } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { catchError, map, of, switchMap } from 'rxjs';
import { FlightApi } from './flight-api.service';
import { FlightSearchActions } from './flight-search.actions';

// Reducer side (pure): on(FlightSearchActions.searchRequested,
//   (state) => ({ ...state, loading: true }))

export const searchFlights = createEffect(
  (actions$ = inject(Actions), flightApi = inject(FlightApi)) => {
    return actions$.pipe(
      ofType(FlightSearchActions.searchRequested),
      switchMap(({ query }) =>
        flightApi.search(query).pipe(
          map((flights) => FlightSearchActions.searchSucceeded({ flights })),
          catchError((error: { message: string }) =>
            of(FlightSearchActions.searchFailed({ message: error.message }))
          )
        )
      )
    );
  },
  { functional: true }
);

go deeper

for a junior

Recall the contract: a reducer takes state and an action and synchronously returns new state with no side effects, so requests live in effects that answer with new actions.

for a middle

Walk the full loop: dispatch, reducer marks loading, Actions emits after reduction, effect filters with ofType, calls the API, maps to success or failure, reducer stores the result.

for a senior

Explain what the split buys in production: replayable action logs, one owner for each API call, and effects testable by feeding actions in and asserting actions out.

for a principal

Weigh what the indirection costs, an extra action pair and file per request, against one owner per API call, a replayable action log and effects that several features reuse.

## Three candidate homes for one request A flight-search page has a search box. When the traveller submits a query, something has to call the flight API, wait for the answer and put the flights into the store. In an NgRx application there are three places that code could live: the **component**, the **reducer**, or an **effect**. Interviewers ask this question to see whether you understand why NgRx gives side effects a place of their own. A **side effect** is anything a function does besides computing its return value: an HTTP request, a timer, a navigation, a write to `localStorage`, a log line. ## Why the reducer cannot do it A **reducer** in NgRx is a function built with `createReducer` and `on()` that receives the current state and one action and returns the next state. The store calls it synchronously for every dispatched action. That contract rules out a request: - **It must return now.** The reducer's return value *is* the new state. A request answers later, after the reducer has already returned, and nothing would receive the answer. - **It must be pure.** Given the same state and action it must produce the same result. That is what lets the Redux DevTools browser extension replay a recorded session; a reducer that fired requests would send them again on every replay. - **It must be cheap and predictable to test.** A pure reducer is tested with plain inputs and outputs, no HTTP mocking. The reducer still has a job in the search: it can react to `searchRequested` by setting `loading: true`, and to `searchSucceeded` by storing the flights. ## Why not the component The component could call the API directly and dispatch the result. It works, but: - every component that can start a search repeats the same loading, error and retry logic; - the request is tied to the component's lifetime and the knowledge of *how* flights are fetched leaks into the view; - the action log no longer shows what happened between the query and the results, which is much of the debugging value of a store. ## What an effect is An **effect** is a long-running observable that NgRx subscribes to for you. You declare it with `createEffect` and register it with `provideEffects`. The flow for one search is: 1. The component dispatches `FlightSearchActions.searchRequested({ query })`. 2. The store runs the reducers first; the search slice sets `loading: true`. 3. Only then does the injectable `Actions` stream emit the same action to every effect. 4. The search effect keeps it with `ofType(FlightSearchActions.searchRequested)`, calls the flight API, and maps the response to `searchSucceeded({ flights })`, or catches the error and returns `searchFailed({ error })`. 5. NgRx dispatches that new action; the reducer stores the flights or the error, and selectors hand the result to the view. | Step | Who | Pure? | |---|---|---| | Declare intent | Component, `store.dispatch` | yes, just an action | | Mark loading | Reducer, `on(searchRequested)` | yes | | Call the API | Effect, `createEffect` + `ofType` | no, that is its job | | Store the result | Reducer, `on(searchSucceeded)` | yes | | Show it | Selectors read by the view | yes | ## What each side gains The component's store code shrinks to dispatching an action and reading a selector. The reducer stays pure and replayable. The effect is the single place that knows about the flight API, so a second screen that dispatches the same action gets the same behaviour for free, and an effect can be tested by feeding it actions and checking the actions it emits. Effects are not limited to HTTP. The NgRx documentation shows effects built on other observable sources, such as DOM events, and effects that dispatch nothing at all, for example a navigation after a booking is saved. ## Misreadings to avoid - An effect does not replace the reducer: the success action it emits still needs an `on()` handler, or the flights never reach the state. - An NgRx effect is not Angular's `effect()` from `@angular/core`; that one reacts to signal changes, while an NgRx effect reacts to dispatched actions. - Effects see an action after the reducers, never before, so a state read inside the effect already reflects that action.

  • Does an NgRx effect see the state before or after the reducers handled the same action?
    After. The store runs the reducers first and only then emits the action on the `Actions` stream that effects subscribe to. So if the reducer sets `loading: true` for `searchRequested`, a selector the search effect reads while handling that action already sees the flag.
  • Can an NgRx effect start from something other than a dispatched action?
    Yes. An effect is any observable passed to `createEffect`; the NgRx docs show one built on `fromEvent(document, 'click')`. It usually still maps to actions, or is declared with `{ dispatch: false }` when it only performs the side effect.

A reducer is the ledger clerk who records each order slip the moment it arrives and never leaves the desk; an effect is the runner who reads each slip after it has been recorded, goes out to fetch the flights, and comes back with a new slip saying what happened.

saying these in an interview costs you the question

  • A reducer can start the API call and update the state when the response arrives.
  • Effects run before the reducers, so they only ever see the previous state.
  • Once an effect handles the search, the success action needs no reducer handler.
  • An NgRx effect is the same thing as Angular's effect() on signals.
  • Effects are only for HTTP calls; timers and navigation belong in reducers.
open as a page

In NgRx's @ngrx/entity, why does a kanban board keep its cards as an ids array plus an entities map instead of one array?

level: juniorimportance: must knowfreq 44%

basics

~20 s

EntityState splits a collection into ids, which hold the order, and entities, a map keyed by id, so a card is found by key instead of a scan, is stored once, and can change without rebuilding the order.

open as a page

In NgRx 22, why must an on() handler inside createReducer return a new state object instead of mutating the slice it receives?

level: juniorimportance: must knowfreq 62%

basics

~20 s

An NgRx reducer is a pure function whose new object is how the store detects a change: selectors and select() compare by reference. In development the default immutability check freezes state, so a mutation throws instead of failing silently.

open as a page

In NgRx 22, why should an invoicing dashboard's components read the invoice slice through selectors instead of mapping store state themselves?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Selectors keep knowledge of the state shape in one file, compute derived values such as invoice totals once and cache them, compose into larger reads, and are pure functions that can be tested without a component.

open as a page

What is an NgRx SignalStore, and how does it differ from an Angular service that keeps its own writable signals?

level: juniorimportance: must knowfreq 62%

basics

~20 s

An NgRx SignalStore is an injectable class built by signalStore() from features: withState turns state slices into signals, withComputed and withMethods add derived values and operations, and by default only the store's own methods can change state.

open as a page

In NgRx, what is an action, and how does a component define one with createAction and send it to the Store?

level: juniorimportance: must knowfreq 66%

basics

~20 s

An NgRx action is a plain object whose type string names an event, such as '[Catalogue Page] Opened'. You declare an action creator with createAction and props, call it to build the object, and pass that object to Store.dispatch.

open as a page

In NgRx 22, how do you unit-test a reducer built with createReducer, and what should the test assert about the returned state?

level: juniorimportance: must knowfreq 52%

basics

~20 s

An NgRx reducer is a pure function, so call it directly with a start state and an action from its creator, then assert the new values, a new object for handled actions and the same reference for unknown ones.

open as a page

In NgRx 22, how do you define an orders slice with createFeature and register it only when a lazy admin route loads?

level: middleimportance: must knowfreq 50%

basics

~20 s

createFeature({ name: 'orders', reducer }) bundles the reducer with a feature selector and one selector per state property. Putting provideState(ordersFeature) in the admin route's providers adds the slice to the single store when that route first activates.

open as a page

In NgRx 22, how do createFeatureSelector and createSelector compose an invoice dashboard's overdue count, and what does each memoized selector cache?

level: middleimportance: must knowfreq 50%

basics

~10 s

createFeatureSelector reads the invoices key; createSelector chains inputs into a projector. Each memoized selector caches only its last arguments and result, compared with ===, and keeps them until the next miss or release().

open as a page

With an NgRx SignalStore, how do you share loading and paging logic between a products store and a users store without a base class?

level: middleimportance: must knowfreq 34%

basics

~10 s

Write a withPaging() function that returns signalStoreFeature(withState(...), withComputed(...)) for the page index, loading flag and error, then add it to both signalStore() calls. Each store gets its own independent copy of that state.

open as a page

In an NgRx SignalStore, how do withState, withComputed and withMethods build the store, and why does the order of features matter?

level: middleimportance: must knowfreq 55%

basics

~20 s

signalStore() applies its features left to right, and each feature's factory receives only the members added before it. withState adds state signals, withComputed derives read-only signals, and withMethods adds operations that call patchState, so a feature cannot use a member declared later.

open as a page

In NgRx, what does createActionGroup generate from a source and an events map, and why does a payload-less event need emptyProps()?

level: middleimportance: must knowfreq 55%

basics

~20 s

createActionGroup returns one action creator per event, named by camel-casing the event name, with type '[Source] Event Name'. A payload-less event needs emptyProps() because the events map needs a value and props<{}>() fails NgRx's empty-object type check.

open as a page

In NgRx, how do you test a component that shows the cart total from the store, using provideMockStore instead of real reducers?

level: middleimportance: must knowfreq 47%

basics

~10 s

Provide provideMockStore from @ngrx/store/testing, pin the cart-total selector with overrideSelector or the selectors option, and assert the rendered total; since no reducers run, verify clicks by spying on store.dispatch.

open as a page

In an NgRx 22 flight-search app, which flattening operator suits the search, save-booking and login effects, and what breaks if the save effect uses switchMap?

level: seniorimportance: must knowfreq 60%

basics

~20 s

Search uses switchMap so a newer query cancels the stale request; save uses concatMap so every booking completes in order; login uses exhaustMap so repeat clicks are ignored. switchMap on save cancels an in-flight save, whose outcome action then never arrives.

open as a page

In NgRx 22, when does an effect need { dispatch: false }, and why does a navigate-after-booking effect built on tap loop without it?

level: middleimportance: should knowfreq 44%

basics

~20 s

An NgRx effect dispatches every value it emits unless created with { dispatch: false }. An effect that only navigates or logs through tap re-emits the action it received, so without that flag it dispatches the same action again and loops.

open as a page

In NgRx 22, how do you write and register a functional effect that saves a flight booking and maps the result to actions?

level: middleimportance: should knowfreq 55%

basics

~20 s

Export a const built with createEffect((actions$ = inject(Actions), api = inject(BookingApi)) => ..., { functional: true }), filter with ofType, map the API result to success or failure actions, and register it with provideEffects at root or in a route's providers.

open as a page

In NgRx, what do createEntityAdapter's selectId and sortComparer options control for kanban cards, and what does a sortComparer cost?

level: middleimportance: should knowfreq 31%

basics

~10 s

selectId tells the adapter which value is a card's key and defaults to entity.id; sortComparer, when given, keeps ids sorted on every write, while the default false keeps insertion order and does less work.

open as a page

In an NgRx reducer, a kanban card moves columns via the entity adapter's updateOne; what changes in state, and what if the id is unknown?

level: middleimportance: should knowfreq 38%

basics

~20 s

updateOne takes { id, changes }, shallow-merges the changes into the stored card and returns state with a new entities map, keeping ids unless the key or sort slot changed. An unknown id returns the same state object.

open as a page

In NgRx 22, how do store.select() and store.selectSignal() differ when a component reads an invoice-total selector, and how does each suppress repeat values?

level: middleimportance: should knowfreq 45%

basics

~10 s

store.select returns an Observable built from map plus distinctUntilChanged, so repeats are dropped by ===. store.selectSignal returns a computed signal over the store's state, compared with Object.is or a supplied equal function.

open as a page

In an NgRx SignalStore that uses withEntities for products keyed by sku, how do addEntity, setEntity and upsertEntity differ, and where does selectId go?

level: middleimportance: should knowfreq 27%

basics

~20 s

For an existing id, addEntity keeps the stored product, setEntity replaces it and upsertEntity merges the given properties. withEntities takes no id selector; pass selectId to each add, set, upsert and update call, or bundle it with entityConfig.

open as a page

With NgRx SignalStore, when do withHooks' onInit and onDestroy run for a store provided at root versus one in a component's providers?

level: middleimportance: should knowfreq 42%

basics

~20 s

A SignalStore's onInit runs once per instance, in its constructor on first injection, inside the injection context. onDestroy runs when the creating injector is destroyed: with the component for a component-provided store, at application teardown for a root store.

open as a page

In NgRx 22, how does provideStore() differ from StoreModule.forRoot(), and where must a standalone app register the root store?

level: middleimportance: should knowfreq 42%

basics

~20 s

provideStore() and StoreModule.forRoot() configure the same root store with the same arguments. provideStore() returns EnvironmentProviders for a standalone app's application config; forRoot() goes in an NgModule's imports. Register the root once; a second registration throws.

open as a page

In NgRx, how do you test a createSelector selector through its projector, and what does a projector test leave unproven?

level: middleimportance: should knowfreq 38%

basics

~10 s

Call selector.projector(...) with plain input values to test the projection logic alone; it never runs the input selectors, so a whole-state call is still needed to prove the selector reads the right slices.

open as a page

In NgRx 22, why does a login effect stop reacting after one failed login when catchError sits on the outer actions$ pipe, and what does NgRx's default error handler do?

level: seniorimportance: should knowfreq 52%

basics

~20 s

An outer catchError replaces the whole effect stream with its fallback, which emits loginFailed and completes, so the effect is finished. NgRx's default handler only resubscribes after uncaught errors, up to 10 times; catch on the inner request instead.

open as a page

In NgRx, how would a date-range picker keep its own state in a ComponentStore, and how do updater, select and effect divide the work?

level: seniorimportance: should knowfreq 33%

basics

~20 s

Provide a ComponentStore subclass in the picker's providers so each picker gets its own store that is torn down with it; updater writes state, select exposes deduplicated Observables, and effect runs async work such as loading blocked days.

open as a page

In NgRx 22, how would you write a meta-reducer that resets every slice, including a lazily registered orders feature, when the admin logs out?

level: seniorimportance: should knowfreq 30%

basics

~20 s

A meta-reducer wraps the root reducer: on the logout action it calls the inner reducer with undefined state, so every slice falls back to its initial state. Registered in provideStore's metaReducers, it also covers features added later by lazy routes.

open as a page

In NgRx 22, which of the store's six runtime checks are on by default, what does each one catch, and when would you enable the others?

level: seniorimportance: should knowfreq 26%

basics

~10 s

NgRx 22 enables strictStateImmutability and strictActionImmutability by default in development; the two serializability checks, strictActionWithinNgZone and strictActionTypeUniqueness start off. Production builds switch all six off, whatever the config says.

open as a page

An NgRx 22 invoicing dashboard's per-customer view recomputes after almost every dispatched action; what defeats createSelector's memoization, and how do you fix it?

level: seniorimportance: should knowfreq 35%

basics

~20 s

createSelector caches one set of arguments compared by ===. It misses when an input selector builds a new array or object each call, when one instance is shared across different parameters, or when a factory creates a fresh selector per read.

open as a page

Why did NgRx deprecate selectors with props in favour of factory selectors, and how would you migrate a customer-invoices selector that takes a customerId prop?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Props selectors share one single-entry cache across every caller's props, type poorly and memoize child selectors badly. Replace them with a factory, (customerId) => createSelector(...), one instance per reader. Deprecated since NgRx 12, slated for removal in v23.

open as a page

showing 1–30 of 45