Sitemap

RxJS and NgRx: How They Work Together for State Management

6 min readSep 28, 2024

--

When building large-scale Angular applications, managing the state of your app becomes crucial to ensure efficiency, scalability, and maintainability. Two powerful tools that can work together to handle state management in Angular are RxJS and NgRx. While RxJS enables reactive programming through asynchronous data streams, NgRx provides a predictable state management solution for Angular applications.
RxJS and NgRx: How They Work Together for State Management

When building large-scale Angular applications, managing the state of your app becomes crucial to ensure efficiency, scalability, and maintainability.

Two powerful tools that can work together to handle state management in Angular are RxJS and NgRx. While RxJS enables reactive programming through asynchronous data streams, NgRx provides a predictable state management solution for Angular applications.

In this blog, we will explore how RxJS and NgRx complement each other in managing state, and how you can leverage their capabilities to build robust Angular applications.

1. What is RxJS?

RxJS (Reactive Extensions for JavaScript) is a library for managing asynchronous data streams in a declarative, functional way.

It allows you to work with real-time events, handle API requests, and manipulate continuous streams of data using operators like map, filter, merge, and more.

Key Features of RxJS

  • Reactive Programming: Allows your application to react to changes in data streams.
  • Asynchronous Data Handling: Manages events, API responses, or any other asynchronous data without blocking the main thread.
  • Operators: Provides a wide range of functional operators to transform, filter, and combine data streams.
  • Observables: Core concept in RxJS; represents a data stream that can emit multiple values over time.

2. What is NgRx?

NgRx is a state management library built specifically for Angular applications.

It is inspired by the Redux architecture, which focuses on maintaining a single source of truth (the state) for the entire application.

NgRx leverages RxJS to handle reactive state changes, providing a predictable, immutable, and centralized way to manage the application’s state.

Key Features of NgRx

  • Single State Tree: The application state is stored in a single object that can be accessed globally.
  • Immutability: Ensures that the state is immutable and only changed through actions and reducers.
  • Actions and Reducers: Actions describe the events that change the state, while reducers specify how the state transitions in response to actions.
  • Effects: Handles side effects (e.g., API calls) asynchronously using RxJS.
  • Selectors: Extract specific parts of the state tree for use in components.

3. How RxJS and NgRx Work Together

NgRx heavily relies on RxJS under the hood to implement its reactive state management features.

Here’s how they complement each other:

  1. Reactive Data Flow: RxJS observables are used to manage the flow of actions and state changes throughout the NgRx store. Every time the state is updated, it is done reactively.
  2. Async State Management: With RxJS, NgRx can manage asynchronous operations, such as making HTTP requests or handling real-time events. RxJS observables ensure that these operations are non-blocking and can emit multiple values over time.
  3. State Observation: In an NgRx application, components can observe specific parts of the state using selectors. These selectors use RxJS observables to reactively update the component whenever the state changes.
  4. Effects Handling: NgRx effects, which handle side effects like API calls, rely on RxJS observables to trigger actions in response to the completion of these side effects.

4. Core Concepts in NgRx and RxJS Integration

a. Actions

In NgRx, actions represent events that describe changes to the state. Actions are dispatched from components and effects, and they are processed by reducers.

// Example of an NgRx Action
export const loadUsers = createAction('[User API] Load Users');

b. Reducers

Reducers define how the application state changes in response to dispatched actions. Each reducer function takes the current state and an action, then returns a new state based on the action.

export const userReducer = createReducer(
initialState,
on(loadUsersSuccess, (state, { users }) => ({ ...state, users }))
);

c. Selectors

Selectors are used to extract specific parts of the state tree, which can be observed by components.

export const selectAllUsers = createSelector(
(state: AppState) => state.users,
(users) => users.all
);

Components can then subscribe to these selectors using RxJS observables:

this.users$ = this.store.select(selectAllUsers);

d. Effects

Effects handle side effects in NgRx applications, such as making HTTP requests or managing real-time data. RxJS operators like switchMap, catchError, and map are used to manage these side effects reactively.

@Injectable()
export class UserEffects {
loadUsers$ = createEffect(() =>
this.actions$.pipe(
ofType(loadUsers),
switchMap(() =>
this.userService.getAll().pipe(
map(users => loadUsersSuccess({ users })),
catchError(error => of(loadUsersFailure({ error })))
)
)
)
);
constructor(private actions$: Actions, private userService: UserService) {}
}

Here, the effect listens for the loadUsers action, makes an HTTP request via userService, and either dispatches a loadUsersSuccess or loadUsersFailure action based on the outcome. The entire process is powered by RxJS observables.

5. Practical Example: NgRx with RxJS for State Management

Let’s walk through a practical example where we integrate RxJS and NgRx for managing the state of a user list in an Angular application.

Step 1: Define Actions

We’ll define three actions for loading users: a request, success, and failure action.

// actions/user.actions.ts
import { createAction, props } from '@ngrx/store';
import { User } from './user.model';

export const loadUsers = createAction('[User API] Load Users');

export const loadUsersSuccess = createAction(
'[User API] Load Users Success',
props<{ users: User[] }>()
);

export const loadUsersFailure = createAction(
'[User API] Load Users Failure',
props<{ error: any }>()
);

Step 2: Create a Reducer

We’ll define a reducer that updates the state based on the actions.

// reducers/user.reducer.ts
import { createReducer, on } from '@ngrx/store';
import { loadUsers, loadUsersSuccess, loadUsersFailure } from '../actions/user.actions';

export const initialState = {
users: [],
loading: false,
error: null
};

export const userReducer = createReducer(
initialState,
on(loadUsers, state => ({ ...state, loading: true })),
on(loadUsersSuccess, (state, { users }) => ({ ...state, loading: false, users })),
on(loadUsersFailure, (state, { error }) => ({ ...state, loading: false, error }))
);

Step 3: Define an Effect for API Calls

We’ll use RxJS operators to handle the API call asynchronously and dispatch success or failure actions.

// effects/user.effects.ts
import { Injectable } from '@angular/core';
import { Actions, ofType, createEffect } from '@ngrx/effects';
import { UserService } from '../services/user.service';
import { loadUsers, loadUsersSuccess, loadUsersFailure } from '../actions/user.actions';
import { of } from 'rxjs';
import { map, switchMap, catchError } from 'rxjs/operators';

@Injectable()
export class UserEffects {
loadUsers$ = createEffect(() =>
this.actions$.pipe(
ofType(loadUsers),
switchMap(() =>
this.userService.getUsers().pipe(
map(users => loadUsersSuccess({ users })),
catchError(error => of(loadUsersFailure({ error })))
)
)
)
);
constructor(private actions$: Actions, private userService: UserService) {}
}

Step 4: Use Selectors to Access the State in Components

Components can access the state using selectors and RxJS observables.

// components/user-list.component.ts
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { loadUsers } from '../actions/user.actions';
import { selectAllUsers } from '../selectors/user.selectors';

@Component({
selector: 'app-user-list',
templateUrl: './user-list.component.html'
})
export class UserListComponent implements OnInit {
users$ = this.store.select(selectAllUsers);
constructor(private store: Store) {}
ngOnInit() {
this.store.dispatch(loadUsers());
}
}

6. Conclusion

RxJS and NgRx are a powerful combination for managing state in Angular applications.

RxJS provides the reactive tools necessary to handle asynchronous data, while NgRx gives structure and predictability to state management.

Together, they enable you to build scalable, maintainable, and reactive Angular applications.

By leveraging the power of observables, actions, reducers, selectors, and effects, you can create efficient workflows for managing both the state and side effects in your Angular apps.

This integration ensures that your application remains responsive and easy to reason about as it scales.

--

--

Mayur Koshti
Mayur Koshti

Written by Mayur Koshti

Dynamic Programmer. I like to write on coding solution and latest tech-related information. My aim is to provide the best knowledge in easy way.

No responses yet