Angular NgRx Actions, Reducers & Effects
Learn NgRx Actions, Reducers, and Effects with architecture diagrams, real-world examples, Signals integration, best practices, common mistakes, and interview questions for Angular 20.
NgRx follows the Redux architecture, where every state change flows through a predictable pipeline.
Instead of components directly modifying application state, NgRx introduces three important building blocks:
- Actions → Describe what happened.
- Reducers → Update application state.
- Effects → Handle asynchronous operations.
Together, these components provide a scalable, testable, and maintainable architecture for enterprise Angular applications.
Table of Contents
- What are Actions?
- What are Reducers?
- What are Effects?
- NgRx Data Flow
- Creating Actions
- Creating Reducers
- Creating Effects
- Success & Failure Actions
- Signals Integration
- Enterprise Banking Example
- Best Practices
- Common Mistakes
- Top 10 Interview Questions
- Summary
NgRx Architecture
flowchart LR
Component
Component --> DispatchAction
DispatchAction --> Action
Action --> Effect
Effect --> API
API --> SuccessAction
SuccessAction --> Reducer
Reducer --> Store
Store --> Selector
Selector --> Component
What are Actions?
An Action represents an event that occurred inside the application.
Examples
- User logged in
- Customer loaded
- Product added
- Order cancelled
- Transaction completed
Actions describe what happened, not how it should happen.
Action Characteristics
- Immutable
- Serializable
- Simple objects
- No business logic
- Easy to debug
Creating an Action
import { createAction } from '@ngrx/store';
export const loadCustomers = createAction(
'[Customer] Load Customers'
);
Action with Payload
import { createAction, props } from '@ngrx/store';
export const addCustomer = createAction(
'[Customer] Add Customer',
props<{ customer: Customer }>()
);
Payload carries additional data.
Dispatching an Action
this.store.dispatch(loadCustomers());
With payload
this.store.dispatch(
addCustomer({
customer: newCustomer
})
);
Action Flow
flowchart LR
ButtonClick
ButtonClick --> Dispatch
Dispatch --> Action
Action --> Effect
What is a Reducer?
A Reducer is a pure function responsible for updating application state.
Reducers receive:
- Current State
- Action
Reducers return:
- New State
Reducers never modify the existing state.
Reducer Architecture
flowchart LR
Action --> Reducer
Reducer --> NewState
NewState --> Store
Reducer Example
export interface CustomerState {
customers: Customer[];
loading: boolean;
}
export const initialState: CustomerState = {
customers: [],
loading: false
};
Reducer
export const customerReducer = createReducer(
initialState,
on(loadCustomers, state => ({
...state,
loading: true
})),
on(loadCustomersSuccess, (state, { customers }) => ({
...state,
customers,
loading: false
}))
);
Why Reducers Must Be Pure
Reducers should:
- Return new state
- Never call APIs
- Never modify existing objects
- Never perform asynchronous work
Pure reducers make state changes predictable and easy to test.
Immutable Updates
❌ Wrong
state.customers.push(customer);
✅ Correct
return {
...state,
customers: [
...state.customers,
customer
]
};
What are Effects?
Effects handle:
- HTTP requests
- Authentication
- File uploads
- Logging
- Notifications
- Navigation
Effects keep reducers pure.
Effect Architecture
flowchart LR
Action
Action --> Effect
Effect --> RESTAPI
RESTAPI --> SuccessAction
RESTAPI --> FailureAction
SuccessAction --> Reducer
FailureAction --> Reducer
Creating an Effect
@Injectable()
export class CustomerEffects {
loadCustomers$ = createEffect(() =>
this.actions$.pipe(
ofType(loadCustomers),
switchMap(() =>
this.customerService.getCustomers().pipe(
map(customers =>
loadCustomersSuccess({
customers
})
),
catchError(error =>
of(
loadCustomersFailure({
error
})
)
)
)
)
)
);
constructor(
private actions$: Actions,
private customerService: CustomerService
) {}
}
Success and Failure Actions
Success
export const loadCustomersSuccess =
createAction(
'[Customer] Load Success',
props<{
customers: Customer[]
}>()
);
Failure
export const loadCustomersFailure =
createAction(
'[Customer] Load Failure',
props<{
error: unknown
}>()
);
Complete Flow
sequenceDiagram
participant User
participant Component
participant Store
participant Effect
participant API
participant Reducer
User->>Component: Click Load
Component->>Store: Dispatch Action
Store->>Effect: Action Observed
Effect->>API: HTTP Request
API-->>Effect: Response
Effect->>Store: Success Action
Store->>Reducer: Update State
Reducer-->>Component: Updated Data
Multiple Effects
Applications often have multiple Effects.
Examples
- Authentication
- Customers
- Orders
- Products
- Payments
- Notifications
Each feature should own its own Effects.
Signals Integration
Selectors can be converted into Signals.
customers = toSignal(
this.store.select(
selectCustomers
)
);
Template
@for (
customer of customers();
track customer.id
) {
<div>
{{ customer.name }}
</div>
}
Signals improve template readability while NgRx manages global state.
Enterprise Banking Example
Customer Dashboard
Features
- Authentication
- Accounts
- Loans
- Cards
- Investments
- Transactions
Architecture
flowchart TD
Dashboard
Dashboard --> DispatchAction
DispatchAction --> CustomerEffect
CustomerEffect --> BankingAPI
BankingAPI --> SuccessAction
SuccessAction --> Reducer
Reducer --> Store
Store --> Signals
Signals --> Dashboard
Benefits
- Predictable workflow
- Centralized state
- Easier debugging
- Better scalability
- Cleaner architecture
Folder Structure
store/
├── actions/
│ ├── customer.actions.ts
│ ├── auth.actions.ts
│
├── reducers/
│ ├── customer.reducer.ts
│
├── effects/
│ ├── customer.effects.ts
│
├── selectors/
│ ├── customer.selectors.ts
│
└── models/
Performance Best Practices
| Practice | Benefit |
|---|---|
| Keep Actions simple | Better readability |
| Keep Reducers pure | Predictable updates |
| Handle APIs in Effects | Separation of concerns |
| Use immutable state | Efficient change detection |
| Split features | Better scalability |
| Convert Selectors to Signals | Cleaner templates |
| Use OnPush | Faster rendering |
| Use DevTools | Easier debugging |
Common Mistakes
Calling APIs in Reducers
❌ Wrong
Reducers should never perform asynchronous operations.
Business Logic Inside Actions
Actions only describe events.
Keep business logic in:
- Effects
- Services
Mutating State
❌ Wrong
state.loading = true;
Always return a new state object.
One Huge Reducer
Split reducers by feature.
Examples
- Auth
- Orders
- Customers
- Products
Ignoring Failure Actions
Always create both:
- Success Actions
- Failure Actions
This enables proper error handling and improves user experience.
Actions vs Reducers vs Effects
| Feature | Actions | Reducers | Effects |
|---|---|---|---|
| Describe Events | ✅ | ❌ | ❌ |
| Update State | ❌ | ✅ | ❌ |
| Call APIs | ❌ | ❌ | ✅ |
| Pure Function | N/A | ✅ | ❌ |
| Async Work | ❌ | ❌ | ✅ |
| Dispatch Actions | ✅ | ❌ | ✅ |
Advantages
| Component | Benefit |
|---|---|
| Actions | Predictable event model |
| Reducers | Immutable state updates |
| Effects | Clean async workflows |
| Store | Single source of truth |
| Signals | Reactive UI |
| DevTools | Time-travel debugging |
Top 10 Actions, Reducers & Effects Interview Questions
1. What is an Action in NgRx?
Answer
An Action is an immutable object that represents an event occurring in the application, such as loading data or submitting a form.
2. What is a Reducer?
Answer
A Reducer is a pure function that receives the current state and an Action, then returns a new immutable state.
3. Why must Reducers be pure?
Answer
Pure reducers always produce the same output for the same input, making state changes predictable, testable, and easy to debug.
4. What are Effects?
Answer
Effects handle asynchronous operations such as HTTP requests, authentication, navigation, logging, and notifications without polluting reducers.
5. Why shouldn't APIs be called inside Reducers?
Answer
Reducers must remain synchronous and side-effect free. API calls belong in Effects because they involve asynchronous operations.
6. What is the complete NgRx flow?
Answer
Component → Action → Effect (optional) → Success/Failure Action → Reducer → Store → Selector → Component
7. Why are Success and Failure Actions important?
Answer
They allow applications to update loading states, display errors, retry failed requests, and keep asynchronous workflows predictable.
8. How do Signals integrate with NgRx?
Answer
Selectors can be converted into Signals using toSignal(), allowing Angular templates to consume Store data with Signal-based reactivity.
9. What are common mistakes when using Actions, Reducers, and Effects?
Answer
- Calling APIs inside reducers
- Mutating state
- Adding business logic to actions
- Creating oversized reducers
- Ignoring failure actions
10. What is the recommended enterprise architecture?
Answer
Use feature-based Actions, Reducers, Effects, and Selectors together with immutable state, NgRx DevTools, Signals for UI consumption, and OnPush Change Detection for scalable enterprise applications.
Interview Cheat Sheet
| Requirement | Recommended Solution |
|---|---|
| User Events | Actions |
| State Updates | Reducers |
| HTTP Requests | Effects |
| Global State | Store |
| Read State | Selectors |
| UI Reactivity | Signals |
| Change Detection | OnPush |
| Error Handling | Failure Actions |
| Debugging | NgRx DevTools |
| Enterprise Apps | Feature-based NgRx Architecture |
Summary
Actions, Reducers, and Effects form the core of NgRx's predictable state management architecture. Actions describe events, Reducers update immutable state, and Effects manage asynchronous work such as API communication. This separation of responsibilities results in applications that are easier to understand, test, debug, and scale. Modern Angular applications frequently combine NgRx for global state with Signals for reactive UI consumption, delivering a clean and high-performance architecture.
Key Takeaways
- ✔ Actions describe what happened.
- ✔ Reducers are pure functions that return new immutable state.
- ✔ Effects handle asynchronous operations and side effects.
- ✔ Components dispatch Actions instead of changing state directly.
- ✔ Always keep Reducers synchronous and side-effect free.
- ✔ Use Success and Failure Actions for robust async workflows.
- ✔ Convert Selectors into Signals for cleaner templates.
- ✔ Organize NgRx by feature for better scalability.
- ✔ Use OnPush and immutable updates for optimal performance.
- ✔ Actions, Reducers, and Effects are among the most frequently asked Angular interview topics.
Next Article
➡️ 99-NgRx-Selectors-QA.md