As Angular applications grow in complexity, managing state becomes increasingly challenging. NgRx provides a predictable state container based on the Redux pattern.
Core Concepts
NgRx follows a unidirectional data flow:
- Components dispatch Actions
- Reducers handle actions and return new State
- Selectors derive data from the Store
- Effects handle side effects (API calls, etc.)
Setting Up the Store
// actions
export const loadUsers = createAction('[Users] Load');
export const loadUsersSuccess = createAction(
'[Users] Load Success',
props<{ users: User[] }>()
);
// reducer
export const usersReducer = createReducer(
initialState,
on(loadUsersSuccess, (state, { users }) => ({
...state,
users,
loading: false,
}))
);
// selectors
export const selectUsers = createSelector(
selectUsersState,
(state) => state.users
);
Effects for Side Effects
@Injectable()
export class UsersEffects {
loadUsers$ = createEffect(() =>
this.actions$.pipe(
ofType(loadUsers),
switchMap(() =>
this.usersService.getAll().pipe(
map(users => loadUsersSuccess({ users })),
catchError(error => of(loadUsersFailure({ error })))
)
)
)
);
}
When to Use NgRx
- Shared state accessed by many components
- State that needs to be persisted or rehydrated
- Complex state transitions
- When you need time-travel debugging
For simpler cases, Angular Signals or services with BehaviorSubject might be sufficient.