Angular NgRx
โฑ๏ธ ~18-minute bite ยท solve the sandbox to master
5-Year-Old Metaphor
โ The physical, real-world picture. No jargon.NgRx = a post office.Actions = addressed envelopes โ you drop them in the outbox, you don't deliver them yourself.
Reducers = the sorting room โ reads the address (action type), updates the right PO box (state slice). Pure, deterministic, testable. Effects = the international courier โ handles deliveries that require going outside (HTTP, localStorage, WebSocket). Dispatches a new envelope when done. Store = the PO box grid โ single source of truth. Selectors = your key โ only opens your box, and caches the result until the contents change.
Interactive Sandbox
โ Move something, see it react instantly.Current State
Reducer Case
on() handler that matched
Action Log
No actions yet
Modern NgRx uses createFeature() and provideStore() in ApplicationConfig. The old StoreModule.forRoot() still works but is the NgModule path.
| 1 | // โ Modern standalone setup (NgRx 15+) |
| 2 | // app.config.ts |
| 3 | import { ApplicationConfig } from '@angular/core'; |
| 4 | import { provideStore } from '@ngrx/store'; |
| 5 | import { provideEffects } from '@ngrx/effects'; |
| 6 | import { provideStoreDevtools } from '@ngrx/store-devtools'; |
| 7 | import { counterFeature } from './counter/counter.feature'; |
| 8 | import { CounterEffects } from './counter/counter.effects'; |
| 9 | ย |
| 10 | export const appConfig: ApplicationConfig = { |
| 11 | providers: [ |
| 12 | provideStore(), // root store (empty) |
| 13 | provideState(counterFeature), // register feature slice |
| 14 | provideEffects(CounterEffects), // register effects class |
| 15 | provideStoreDevtools({ |
| 16 | maxAge: 25, |
| 17 | logOnly: !isDevMode(), |
| 18 | }), |
| 19 | ], |
| 20 | }; |
| 21 | ย |
| 22 | // โ Old NgModule setup (still valid, not recommended for new code) |
| 23 | @NgModule({ |
| 24 | imports: [ |
| 25 | StoreModule.forRoot({ counter: counterReducer }), |
| 26 | EffectsModule.forRoot([CounterEffects]), |
| 27 | StoreDevtoolsModule.instrument({ maxAge: 25 }), |
| 28 | ], |
| 29 | }) |
| 30 | export class AppModule {} |
| 31 | ย |
| 32 | // createFeature() auto-generates selectors from the state shape |
| 33 | export const counterFeature = createFeature({ |
| 34 | name: 'counter', |
| 35 | reducer: counterReducer, |
| 36 | }); |
| 37 | // Generates: counterFeature.selectCounterState, counterFeature.selectCount, etc. |
- โprovideState() registers a feature slice in standalone โ equivalent to StoreModule.forFeature()
- โcreateFeature() auto-derives selectors for every top-level state property
- โStoreDevtools connects to Redux DevTools browser extension โ essential for debugging
Challenge
Explore all 5 NgRx patterns. Use the action dispatcher to watch the state machine in action.
Why Should I Care?
โ The exact interview question + the bug it kills.Q: When is NgRx overkill?
Small apps, local UI state (open/close, loading flags, selected item). Use component state or signals/services instead. NgRx adds ~10โ15KB and meaningful boilerplate. Reach for it when multiple disconnected components share state that changes from multiple sources.
Q: What's NgRx Signals Store?
@ngrx/signals is a lightweight signal-based store without RxJS effects boilerplate. Define state with signalStore(), add methods directly, use withComputed() for derived values. No actions/reducers/effects ceremony โ closer to Zustand or Pinia.
Q: How does createSelector memoization work?
It checks each input selector's output by reference equality. If none changed, it returns the cached projector result without re-running. One cache entry per selector instance โ selector factories (functions returning selectors) avoid cache collisions when called with different arguments.
The Deep Dive
โ Spec refs, engine internals, the minutiae.NgRx internals, ecosystem packages, and the signals-based future:
- 01
createFeature() auto-generates one selectX selector for each top-level state property. Use extraSelectors to add derived ones in the same file.
- 02
Memoization internals: NgRx stores the last [inputs, result] pair. On each select(), it runs input selectors, compares outputs with the cached inputs using ===, and returns the cached result if unchanged.
- 03
NgRx Entity (@ngrx/entity): EntityAdapter manages normalized collections. selectAll(), selectEntities(), selectTotal() out of the box. Handles add/update/remove/upsert without writing spread logic.
- 04
@ngrx/component-store: local state scoped to a component tree. No global store โ great for feature-level state that doesn't need to survive navigation.
- 05
NgRx Signals Store API: signalStore({ withState({ count: 0 }), withComputed(({ count }) => ({ doubled: computed(() => count() * 2) })), withMethods(...) })
Interview Questions
โ Real questions from real interviews โ with answers.NgRx for complex shared state across disconnected features; use signals/services for co-located or simple state.
'[Source] Event' format groups actions by source in DevTools and prevents collisions across features.
Impure reducers break Redux DevTools time-travel and make state unpredictable and untestable.
It caches one [inputs, result] pair and recomputes only when any input selector's output changes by reference.
dispatch: false for side-effect-only effects. Forgetting causes the effect's emissions to be dispatched as actions, often causing infinite loops.
@ngrx/signals is a lightweight signal-based store without actions/reducers/effects โ closer to Zustand than Redux.
Memory Game
โ Quick quiz โ lock the concept in long-term memory.What does provideEffects([CounterEffects]) register in a standalone Angular app?