NgRx Basics
What is NgRx?
NgRx is a state management library for Angular based on Redux principles. It provides a predictable state container using RxJS.
Core Concepts
Store
Central state container - single source of truth for your app state.
Actions
Events that describe what happened in the app.
import { createAction, props } from '@ngrx/store';
export const loadTodos = createAction('[Todo] Load Todos');
export const addTodo = createAction(
'[Todo] Add Todo',
props<{ text: string }>()
);
export const toggleTodo = createAction(
'[Todo] Toggle Todo',
props<{ id: string }>()
);
Reducers
Pure functions that take the current state and an action, return new state.
import { createReducer, on } from '@ngrx/store';
import * as TodoActions from './todo.actions';
export interface Todo {
id: string;
text: string;
completed: boolean;
}
export interface TodoState {
todos: Todo[];
}
const initialState: TodoState = {
todos: [],
};
export const todoReducer = createReducer(
initialState,
on(TodoActions.addTodo, (state, { text }) => ({
...state,
todos: [
...state.todos,
{ id: Date.now().toString(), text, completed: false },
],
})),
on(TodoActions.toggleTodo, (state, { id }) => ({
...state,
todos: state.todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
),
}))
);
Selectors
Functions to select slices of state.
import { createSelector, createFeatureSelector } from '@ngrx/store';
import { TodoState } from './todo.reducer';
export const selectTodoState = createFeatureSelector<TodoState>('todos');
export const selectAllTodos = createSelector(
selectTodoState,
state => state.todos
);
export const selectCompletedTodos = createSelector(selectAllTodos, todos =>
todos.filter(todo => todo.completed)
);
Setup
1. Install NgRx
npm install @ngrx/store @ngrx/store-devtools
2. Configure Store
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideStore } from '@ngrx/store';
import { provideStoreDevtools } from '@ngrx/store-devtools';
import { todoReducer } from './state/todo.reducer';
export const appConfig: ApplicationConfig = {
providers: [
provideStore({ todos: todoReducer }),
provideStoreDevtools({ maxAge: 25 }),
],
};
3. Use in Components
import { Component, inject } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import * as TodoActions from './state/todo.actions';
import * as TodoSelectors from './state/todo.selectors';
import { Todo } from './state/todo.reducer';
@Component({
selector: 'app-todos',
standalone: true,
template: `
<input #input (keyup.enter)="addTodo(input.value); input.value = ''" />
<ul>
@for (todo of todos$ | async; track todo.id) {
<li (click)="toggleTodo(todo.id)" [class.completed]="todo.completed">
{{ todo.text }}
</li>
}
</ul>
`,
})
export class TodosComponent {
private store = inject(Store);
todos$: Observable<Todo[]> = this.store.select(TodoSelectors.selectAllTodos);
addTodo(text: string) {
if (text.trim()) {
this.store.dispatch(TodoActions.addTodo({ text }));
}
}
toggleTodo(id: string) {
this.store.dispatch(TodoActions.toggleTodo({ id }));
}
}
When to Use NgRx
✅ Use NgRx when:
- State is shared across many components
- You need time-travel debugging
- Complex state updates require predictability
- Team prefers explicit state management
❌ Don't use NgRx when:
- Simple local component state
- Small applications (use Signals instead)
- Only 1-2 components share state
NgRx with Signals (Modern Approach)
import { Component, inject } from '@angular/core';
import { Store } from '@ngrx/store';
import { toSignal } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-todos',
standalone: true,
template: `
<ul>
@for (todo of todos(); track todo.id) {
<li>{{ todo.text }}</li>
}
</ul>
`,
})
export class TodosComponent {
private store = inject(Store);
todos = toSignal(this.store.select(TodoSelectors.selectAllTodos), {
initialValue: [],
});
}
Key Principles
- Single source of truth - State lives in one store
- State is read-only - Only actions can change state
- Pure functions - Reducers must be pure
- Immutability - Always return new state objects
Quick Reference
// Dispatch action
this.store.dispatch(myAction({ data }));
// Select state
this.data$ = this.store.select(mySelector);
// Select as signal
this.data = toSignal(this.store.select(mySelector));