← Back to Chapters

NgRx Best Practices & Patterns

⚙️ NgRx Best Practices & Patterns

? Quick Overview

NgRx provides a powerful, Redux-inspired way to manage state in Angular applications. By following proven patterns and best practices, you keep your store maintainable, testable, and scalable as your app grows.

? Key Concepts

  • Organize code using a clear, feature-based folder structure.
  • Use consistent naming conventions for actions, reducers, selectors, and effects.
  • Keep reducers pure and selectors reusable and memoized.
  • Use effects for side-effects such as API calls and navigation.
  • Leverage NgRx Entity for collections and performance optimizations.
  • Combine NgRx with OnPush change detection to minimize re-renders.
  • Write unit tests for reducers, selectors, and effects for reliability.

? Folder & State Structure

  • Use a feature-based folder structure like feature/state/ for actions, reducers, selectors, and effects.
  • Keep state slices isolated per feature module to avoid a monolithic store.
  • Use index.ts files to export all feature-related items for cleaner imports.

?️ Naming Conventions

  • Actions: [Feature] Action Description (e.g., [Auth] Login Success).
  • Selectors: selectFeatureNameProperty (e.g., selectAuthUser).
  • Reducers: featureReducer (e.g., authReducer).

? Reducers & Selectors

Selectors provide a reusable, memoized way to read data from the store. They keep components simple and prevent duplicated state-selection logic across the app.

? View Code Example (Selectors)
// auth.selectors.ts - strongly typed selectors for the auth feature
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { AuthState } from './auth.reducer';

export const selectAuthState = createFeatureSelector<AuthState>('auth');

export const selectUser = createSelector(
  selectAuthState,
  (state: AuthState) => state.user
);

⚡ Effects Best Practices

  • Keep side-effects focused: one effect should do one job.
  • Always handle errors using catchError in effects.
  • Use { dispatch: false } when an effect does not dispatch an action.
  • Use RxJS operators like exhaustMap, switchMap, or concatMap appropriately for your use case.
? View Code Example (Effect)
// auth.effects.ts - handle login request and success/error actions
@Injectable()
export class AuthEffects {
  login$ = createEffect(() =>
    this.actions$.pipe(
      ofType(AuthActions.login),
      exhaustMap(action =>
        this.authService.login(action.credentials).pipe(
          map(user => AuthActions.loginSuccess({ user })),
          catchError(error => of(AuthActions.loginFailure({ error })))
        )
      )
    )
  );

  constructor(
    private actions$: Actions,
    private authService: AuthService
  ) {}
}

? Live Output / Explanation

In the selectors example, selectAuthState reads the auth slice from the global store, and selectUser derives the user from that slice. Components subscribe to selectUser to reactively display the current user.

  • Selectors are memoized, so repeated subscriptions with the same state do not recompute.
  • Effects listen for actions such as AuthActions.login and perform async work.
  • On success, the effect dispatches loginSuccess, which reducers use to update the store.
  • On failure, the effect dispatches loginFailure, so the UI can show an error state.

✅ Tips & Best Practices

  • Centralize state logic in reducers and effects to avoid duplication and scattered business rules.
  • Use NgRx Entity for collections to reduce boilerplate and improve list operations and performance.
  • Use OnPush change detection in components to optimize performance with the store.
  • Write unit tests for reducers, selectors, and effects to catch regressions early.
  • Avoid mutating state directly in reducers; always return a new state object.
  • Keep simple synchronous logic in reducers instead of overusing effects.
  • Prefer selectors (not direct store access) in services and components for consistency.
  • Handle errors in every effect pipeline to prevent unhandled runtime errors.

? Try It Yourself

  • Refactor a feature module to follow the recommended feature/state/ folder structure.
  • Create an effect for an API call and handle success and error states with dedicated actions.
  • Use NgRx Entity for a collection of items and implement selectors to retrieve sorted or filtered data.
  • Enable OnPush change detection on your smart and dumb components and observe performance improvements.
  • Add unit tests for at least one reducer, one selector, and one effect in your project.