← Back to Articles         
What is NgRx?

What is NgRx?

Published on 01 Oct 2026     10 min read Angular
State Management

1. What is NgRx?

What is NgRx?

NgRx is a state-management ecosystem for Angular that provides a structured, predictable way to manage application state, especially when state becomes complex and is shared across many parts of an application.

NgRx commonly follows a Redux-style pattern:

Component
    ↓
Dispatch Action
    ↓
Reducer
    ↓
Store
    ↓
Selector
    ↓
Component

For asynchronous work such as API calls, Effects are commonly added:

Component
    ↓
Action
    ↓
Effect
    ↓
ASP.NET Core API
    ↓
Success/Failure Action
    ↓
Reducer
    ↓
Store
    ↓
Selector
    ↓
Component

Why Do We Need NgRx?

For a small application, this may be enough:

Component
   ↓
Service
   ↓
Signal

For example:

@Injectable({
  providedIn: 'root'
})
export class CartService {

  private readonly _count = signal(0);

  readonly count =
    this._count.asReadonly();
}

That's simple and appropriate.

But imagine a large application containing:

Authentication
Products
Shopping Cart
Orders
Payments
Customers
Notifications
Permissions
Admin Dashboard

and many components need to read or update the same state.

The state relationships can become difficult to track:

Component A ──→ Service A
Component B ──→ Service A
Component C ──→ Service B
Service A   ──→ Service C
Service B   ──→ Service C
...

NgRx introduces a more structured data flow.


Core NgRx Concepts

The most important concepts for interviews are:

Concept Purpose
Store Holds application state
Action Describes something that happened
Reducer Calculates new state
Selector Reads/derives state
Effect Handles side effects such as API calls

Easy way to remember:

Action
   ↓
What happened?

Reducer
   ↓
How should state change?

Store
   ↓
Where is state held?

Selector
   ↓
What state does the UI need?

Effect
   ↓
What external/async work is required?

Full Simple NgRx Example

Let's create a simple counter.

First install NgRx Store:

npm install @ngrx/store

counter.actions.ts

import { createAction } from '@ngrx/store';

export const increment =
  createAction('[Counter] Increment');

export const decrement =
  createAction('[Counter] Decrement');

export const reset =
  createAction('[Counter] Reset');

counter.reducer.ts

import {
  createReducer,
  on
} from '@ngrx/store';

import {
  increment,
  decrement,
  reset
} from './counter.actions';

export const initialState = 0;

export const counterReducer =
  createReducer(

    initialState,

    on(
      increment,
      state => state + 1
    ),

    on(
      decrement,
      state => state - 1
    ),

    on(
      reset,
      () => 0
    )

  );

counter.selectors.ts

import {
  createFeatureSelector
} from '@ngrx/store';

export const selectCount =
  createFeatureSelector<number>(
    'count'
  );

app.config.ts

For standalone Angular:

import {
  ApplicationConfig
} from '@angular/core';

import {
  provideStore
} from '@ngrx/store';

import {
  counterReducer
} from './counter.reducer';

export const appConfig:
  ApplicationConfig = {

  providers: [

    provideStore({
      count: counterReducer
    })

  ]

};

app.ts

import {
  Component,
  inject
} from '@angular/core';

import {
  AsyncPipe
} from '@angular/common';

import {
  Store
} from '@ngrx/store';

import {
  increment,
  decrement,
  reset
} from './counter.actions';

import {
  selectCount
} from './counter.selectors';

@Component({
  selector: 'app-root',
  standalone: true,

  imports: [
    AsyncPipe
  ],

  template: `
    <h1>NgRx Counter</h1>

    <h2>
      Count: {{ count$ | async }}
    </h2>

    <button (click)="increment()">
      +
    </button>

    <button (click)="decrement()">
      -
    </button>

    <button (click)="reset()">
      Reset
    </button>
  `
})
export class App {

  private readonly store =
    inject(Store);

  readonly count$ =
    this.store.select(selectCount);

  increment(): void {

    this.store.dispatch(
      increment()
    );

  }

  decrement(): void {

    this.store.dispatch(
      decrement()
    );

  }

  reset(): void {

    this.store.dispatch(
      reset()
    );

  }

}

How Does This Program Work?

Initially:

Store
 ↓
count = 0

User clicks:

+

Component doesn't directly change:

count++;

Instead it dispatches an Action:

this.store.dispatch(
  increment()
);

Flow:

User clicks +
     ↓
Component
     ↓
dispatch(increment())
     ↓
Action
     ↓
Reducer
     ↓
state + 1
     ↓
Store
     ↓
count = 1
     ↓
Selector
     ↓
Component
     ↓
UI displays 1

What is an Action?

An Action describes an event that occurred.

export const increment =
  createAction(
    '[Counter] Increment'
  );

Examples in a real application:

[Products] Load Products
[Products] Load Products Success
[Products] Load Products Failure

[Auth] Login
[Auth] Login Success
[Auth] Logout

[Cart] Add Product
[Cart] Remove Product

Think:

Action = What happened?


What is a Reducer?

A Reducer calculates the next state based on the current state and an action.

on(
  increment,
  state => state + 1
)

For example:

Current State
count = 5

     +

Action
Increment

     ↓

Reducer

     ↓

New State
count = 6

Reducers should be pure functions.

They should not normally perform API calls or other side effects.


What is the Store?

The Store contains the managed application state.

For example:

Store
│
├── Auth
│   ├── user
│   └── isLoggedIn
│
├── Products
│   ├── products
│   ├── loading
│   └── error
│
├── Cart
│   ├── items
│   └── total
│
└── Orders
    ├── orders
    └── selectedOrder

Components interact with the Store through actions and selectors rather than freely modifying shared state.


What is a Selector?

A Selector reads or derives data from the Store.

For example:

readonly count$ =
  this.store.select(selectCount);

Think:

Selector = What state does this component need?

In a shopping application:

Store
   ↓
Cart State
   ↓
Selector
   ↓
Total Cart Count
   ↓
Header Component

What is an Effect?

Effects handle side effects, especially API calls.

This is extremely important when Angular communicates with ASP.NET Core.

Install:

npm install @ngrx/effects

Suppose Angular needs:

GET
https://api.example.com/api/products

The flow can be:

Product Component
       ↓
Load Products Action
       ↓
Effect
       ↓
HttpClient
       ↓
ASP.NET Core Web API
       ↓
Products returned
       ↓
Load Products Success Action
       ↓
Reducer
       ↓
Store
       ↓
Selector
       ↓
Product Component

The reducer doesn't make the HTTP request.

The Effect handles it.


2. When Would You Use NgRx?

You generally use NgRx when the application has complex shared state and complex state transitions where a formal architecture provides enough benefit to justify the additional code and concepts.


Example: Small Application

Suppose your application has:

Home
Products
Contact
Login

and only a few shared values.

You may be perfectly fine with:

Angular Service
      +
Signals

For example:

@Injectable({
  providedIn: 'root'
})
export class AuthState {

  private readonly _user =
    signal<User | null>(null);

  readonly user =
    this._user.asReadonly();

}

Adding full NgRx here may be unnecessary complexity.


Example: Large Application

Suppose you have:

Authentication
Authorization
Products
Categories
Shopping Cart
Orders
Payments
Customers
Notifications
Admin
Reports

and many components depend on the same state.

For example:

                Product State
                     │
       ┌─────────────┼─────────────┐
       ↓             ↓             ↓
Product List    Product Detail    Cart
       │                           │
       └─────────────┬─────────────┘
                     ↓
                  Checkout

NgRx can make state transitions easier to trace and standardize.


Good Situations for NgRx

Consider NgRx when you have:

  1. Large shared application state
  2. Many components reading/updating the same state
  3. Complex state transitions
  4. Many dependent asynchronous operations
  5. A need for predictable one-way state flow
  6. A large development team needing consistent patterns
  7. Complex debugging requirements
  8. State that needs strong testability and traceability

When NgRx May Be Unnecessary

Don't automatically use NgRx simply because the project uses Angular.

For simpler applications:

Component Signals
       ↓
Shared Service + Signals

may be sufficient.

NgRx introduces additional concepts:

Actions
Reducers
Selectors
Effects
Store

That means more structure, but also more complexity.


Signals vs Service + Signals vs NgRx

A useful progression is:

Simple Local State
       ↓
signal()


Shared State
       ↓
Service + Signals


Complex Application State
       ↓
NgRx

But this isn't a mandatory ladder. Use whichever architecture fits the complexity.


NgRx vs Signals

These are not strictly competing technologies.

Signals

Signals are Angular reactive primitives.

count = signal(0);

They answer:

How can a value be reactive?

NgRx

NgRx provides a broader state-management architecture/ecosystem.

It addresses questions such as:

Where is shared state stored?

How are state changes requested?

How are changes calculated?

How are API side effects handled?

How does the UI select state?

Modern NgRx also has APIs that integrate with Angular Signals, so don't think of the choice as simply:

Signals OR NgRx

NgRx Advantages

Predictable State Changes

State follows a structured flow:

Action
 ↓
Reducer
 ↓
Store
 ↓
Selector

Centralized State

Shared state has clearly defined ownership.


Easier Debugging

Actions make it easier to understand:

What happened?
       ↓
What state changed?
       ↓
Why did the UI change?

Testability

Reducers and selectors are straightforward to unit test because much of the logic can be expressed as pure functions.


Good for Large Teams

A standard architecture helps developers follow consistent patterns.


NgRx Disadvantages

More Boilerplate

A feature may involve:

Actions
Reducers
Selectors
Effects

instead of a small service.


Learning Curve

Developers need to understand:

RxJS
Store
Actions
Reducers
Selectors
Effects
Immutable updates

Overkill for Simple Applications

If all you need is:

isMenuOpen = signal(false);

you don't need NgRx just to manage that value.


Angular + ASP.NET Core Example Architecture

For a larger Angular + ASP.NET Core application:

Angular Component
       │
       │ dispatch
       ▼
     Action
       │
       ▼
     Effect
       │
       ▼
   HttpClient
       │
       ▼
ASP.NET Core Web API
       │
       ▼
 SQL Server
       │
       ▼
API Response
       │
       ▼
     Effect
       │
       │ dispatch
       ▼
Success Action
       │
       ▼
    Reducer
       │
       ▼
     Store
       │
       ▼
   Selector
       │
       ▼
Angular Component

This is one of the most important NgRx flows to understand for interviews.


Key Points

  • NgRx is a state-management ecosystem for Angular.
  • It is useful for complex shared application state.
  • NgRx commonly follows Redux-style unidirectional data flow.
  • Store holds managed state.
  • Actions describe events.
  • Reducers calculate new state.
  • Selectors read or derive state.
  • Effects handle side effects such as ASP.NET Core API calls.
  • Components normally dispatch actions rather than directly mutating Store state.
  • Reducers should be pure.
  • NgRx provides predictable and traceable state transitions.
  • NgRx can improve maintainability and testability in complex applications.
  • NgRx adds concepts and boilerplate.
  • Don't use NgRx automatically for every Angular application.
  • Simple applications may be better served by Signals + Angular services.
  • NgRx and Angular Signals can be used together.

Interview Questions & Answers

1. What is NgRx?

NgRx is a state-management ecosystem for Angular that provides structured, predictable state management using concepts such as Store, Actions, Reducers, Selectors, and Effects.


2. What are the main NgRx concepts?

Store
Action
Reducer
Selector
Effect

Store holds state, Actions describe events, Reducers update state, Selectors read state, and Effects handle side effects.


3. What is an Action?

An Action describes something that happened.

createAction(
  '[Products] Load Products'
);

4. What is a Reducer?

A Reducer is a pure function that calculates the next state from the current state and an action.

on(
  increment,
  state => state + 1
)

5. What is a Selector?

A Selector retrieves or derives specific data from the NgRx Store.

this.store.select(selectProducts);

6. What is an Effect?

An Effect handles side effects such as HTTP API calls.

Action
  ↓
Effect
  ↓
HttpClient
  ↓
ASP.NET Core API

7. When should you use NgRx?

Use NgRx when an application has sufficiently complex shared state, many interacting components, complex asynchronous workflows, or needs highly predictable and traceable state transitions.


8. Should every Angular application use NgRx?

No. Smaller applications can often use local Signals or shared services with Signals/RxJS more simply.


9. NgRx vs Signals — what is the difference?

Signals are Angular reactive primitives for representing reactive values. NgRx is a broader state-management architecture/ecosystem providing structured state flow, actions, reducers, selectors, effects, and related tooling.


Interview Quick Answer

NgRx is a state-management ecosystem for Angular used primarily for complex shared application state. Its core concepts are Store, Actions, Reducers, Selectors, and Effects. Components dispatch actions, reducers update state, selectors read state, and effects handle side effects such as ASP.NET Core API calls. For smaller applications, Angular services with Signals may be simpler; NgRx becomes useful when state interactions and application complexity justify a more structured architecture.

← Back to Articles