skip to content

As the lead of an Angular hotel booking app that needs a store library, how would you weigh NGXS against NgRx, and what does each choice cost the team?

level: principalimportance: should knowfreq 22%

answer

  1. classes and DI versus functions
  2. where side effects live
  3. action lifecycle you can await
  4. explicitness versus boilerplate
  5. what the team already knows

basics

~20 s

NGXS trades NgRx's pure reducers and separate effects for state classes whose DI-enabled @Action handlers update state and do async work in one place. That gives less ceremony and awaitable actions, at the cost of a less explicit, less pure design.

solid answer

~50 s

I would compare the two on where logic lives. In NGXS a `@State` class is built by Angular DI, and its `@Action` handlers both write state through a `StateContext` and do async work, returning an Observable or Promise; each action has a lifecycle the caller can observe or await. NgRx's global Store splits the same flow into action creators, pure `createReducer` functions and separate `createEffect` classes, which is more files but makes every state transition a pure, individually testable function. So NGXS buys less ceremony and a gentler RxJS curve; NgRx buys explicitness and a strict separation that scales to many contributors. I would weigh the team's RxJS fluency, how much side-effect orchestration the app has, the testing style we want, and consistency with the organisation's other apps. Whichever we pick, I would pin conventions early: NGXS 22's standalone `provideStore` and `select()`, never the deprecated `@Select`.

go deeper

for a junior

Know the headline difference: NGXS uses state classes whose @Action methods update state and call services, while NgRx uses reducer functions and separate effects.

for a middle

Explain how async work, completion and selectors differ between the two, and name the NGXS 22 standalone APIs you would use.

for a senior

Weigh concrete risks: race handling in parallel NGXS actions, testability of mixed handlers, and migrating deprecated @Select and NgxsModule code.

for a principal

Own the call: tie it to team skills, side-effect complexity, testing strategy and organisational consistency, back it with a spike, and set conventions before the codebase sets them for you.

## The decision in one sentence Both libraries give an Angular app a single global store driven by actions and read through memoized selectors. The real difference is **where the code that changes state lives** and how much structure the library forces on you. This answer assumes the team has already decided a store library is warranted; whether it is at all is a separate question. ## How the two models differ | Concern | NGXS | NgRx global Store | |---|---|---| | Unit of state | A class decorated with `@State`, built by Angular DI | A reducer function built with `createReducer` and `on()` | | Actions | Classes with a `static readonly type` | Creators from `createAction` or `createActionGroup` | | State changes | `@Action` methods writing through `StateContext` | Pure reducer cases returning new state | | Async work | In the same `@Action` method, returning an Observable or Promise | In separate effects built with `createEffect` | | Completion | Each dispatch completes when its handlers finish; lifecycle statuses are observable | `store.dispatch(action)` returns nothing; effects dispatch follow-up actions | | Reading | `@Selector`, `createSelector`, `select()` returning a signal | `createSelector`, `store.select`, `selectSignal` | | DevTools | `withNgxsReduxDevtoolsPlugin()` | `provideStoreDevtools()` | NgRx also ships a lighter, signal-based store in the same family, the SignalStore. It is often the real alternative an NgRx team would weigh against NGXS for feature-level state, and it deserves its own evaluation rather than a footnote here. ## What NGXS buys, and what it costs **Gains:** - **Less ceremony.** One class holds the defaults, the handlers and often the selectors. There is no separate reducer and effects file per feature. - **Angular-native DI.** Handlers call injected services directly, so the store reads like the rest of the Angular code. - **Awaitable actions.** A component can `await` the result of the NGXS 22 `dispatch()` function, or listen with `ofActionSuccessful` or `ofActionErrored`, for flows such as "confirm the booking, then navigate". - **A gentler RxJS curve.** Handlers may return Promises and use `async`/`await`; state operators such as `patch` and `updateItem` cover immutable updates. **Costs:** - **Less purity.** State writes and side effects share a method, so a handler cannot be tested as a pure input-to-output function the way a reducer can. - **Implicit concurrency.** Async actions run in parallel by default; races need `cancelUncompleted`, `ctx.abortSignal` or explicit operators, and the team must know when to use each. - **API churn to manage.** NGXS 18 deprecated `@Select`, and `NgxsModule` is now deprecated in favour of `provideStore`; code written from older tutorials needs a migration plan. ## What NgRx buys, and what it costs - **Gains:** every transition is a pure function; side effects are isolated in effects, whose flattening operator is an explicit decision; the strict action, reducer, effect split scales to many contributors who need one obvious place for everything. - **Costs:** more files per feature, a steeper RxJS requirement for effects, and more indirection for a newcomer following one user interaction through the code. ## How I would decide as a lead 1. **Team skills.** A team fluent in RxJS absorbs NgRx effects easily; a team newer to RxJS ships faster with NGXS handlers. 2. **Shape of the side effects.** Complex orchestration across many actions favours explicit, isolated effects; mostly request-then-store flows favour NGXS handlers. 3. **Testing strategy.** If the team wants pure, table-driven tests of every transition, reducers fit better. 4. **Consistency.** Matching the organisation's other Angular apps lowers onboarding cost more than any API detail. 5. **Exit cost.** Components that only read through selectors and only write by dispatching actions are the cheapest to move to another store later, so enforce that boundary whichever library wins. 6. **A time-boxed spike.** Build one real flow, such as searching rooms and confirming a reservation, in both, and compare file count, test effort and how easily a newcomer follows it. ## Guardrails whichever way you go - Write down the current API as the house style: for NGXS 22, `provideStore`, `provideStates`, `select()`, `dispatch()` and `withNgxs*Plugin` functions, never `@Select` or `NgxsModule`. - Keep selectors in one layer so components never derive store data ad hoc. - Decide the concurrency rule per action kind before the first race appears in production.

  • Your team already uses NGXS but has @Select and NgxsModule everywhere. How would you plan the migration?
    Treat it as incremental, since both are deprecated but still exported in NGXS 22. First switch the root to `provideStore` and route-level `provideStates`, and each `*PluginModule` to its `withNgxs*Plugin` function. Then replace `@Select` fields with `select()` signals or `inject(Store).select(...)`, converting any string or anonymous-function selectors into real `@Selector` methods first. A lint rule banning the old forms stops new usages.
  • What would make you reconsider NGXS after a year in production?
    Signs that its informality stopped paying off: handlers that grew into long orchestration scripts mixing several services, recurring race bugs from parallel async actions, or tests that need heavy mocking because state writes and side effects share a method. At that point either impose stricter conventions, such as thin handlers delegating to services, or reassess the store choice for new features.

saying these in an interview costs you the question

  • NGXS needs a separate effects layer for HTTP calls, just like NgRx.
  • NGXS has no DevTools support, so time-travel debugging requires NgRx.
  • The choice does not matter, since both libraries are the same API under different names.
  • The library with fewer lines of code is always the better choice for a team.
  • NGXS actions cannot be awaited or observed after they are dispatched.