Skip to main content

Command Palette

Search for a command to run...

Demystifying NgRx Effects

A Deep Dive into Different Complexity Levels

Updated
4 min readView as Markdown
Demystifying NgRx Effects
J

Hi there! My name is Jocelyn, and I am the owner of B&M Development Services. With over 8 years of experience in web application development, I am a highly skilled Senior Software Engineer with a strong focus on front-end technologies.

My expertise lies in Angular, Typescript, RxJs, NGRX, and Akita for state management, as well as Jasmine and Karma for front-end testing. I am passionate about effectively managing state in complex applications and have worked with various state management solutions, including NGRX and Akita.

I am also fascinated by the intersection of machine learning and software engineering, and I have experience in AI tool development and prompt engineering. With the potential of LLMs like GPT-4, I believe we are on the cusp of a revolutionary shift in the field.

As the owner of B&M Development Services, I have built a custom UI administrative dashboard for managing AI music stations using Angular, NGRX state management, and Angular Material, mixed with custom styling. I also built an internal application for the implementation of proprietary software solutions around artificial intelligence-based music mixdowns.

Another project I completed was building a custom remote video consultation solution for CVS using Angular version 10, allowing remote pharmacists to provide legally required medical consultations to patients in remote CVS pharmacy locations. This allowed the client to save a significant amount of the cost of employing a pharmacist in slower CVS locations.

At B&M Development Services, we take pride in our ability to deliver high-quality work and help organizations achieve their project goals. Whether it's working on the front-end, back-end, or exploring the possibilities of LLMs, we are committed to staying at the forefront of the industry and contributing to meaningful projects.

My expertise also extends to Node.js and Express.js, and I have experience with alternative technologies such as Jest and Hapi.js. I am a problem-solver at heart and am always excited to collaborate with organizations on new and exciting opportunities.

Thank you for considering B&M Development Services for your project needs. We look forward to the opportunity to work with you!

NgRx is a powerful state management library for Angular applications, based on the principles of Redux. It provides a predictable state container, making it easier to manage and track application state changes. One of the core concepts of NgRx is Effects, which handle side effects such as API calls and other asynchronous tasks.

In this blog post, we will explore how NgRx Effects files work and their purpose. We will dive into three different levels of complexity, from a straightforward example to more complex scenarios involving data retrieval from the store and combining multiple actions or HTTP requests.

  1. Straightforward Effect

A basic NgRx Effect is simply an Observable that listens for a specific action and responds with a new action. Let's start by creating a simple effect that listens for a 'LOAD_DATA' action and, upon receiving it, dispatches a 'LOAD_DATA_SUCCESS' action with the fetched data.

First, we will create the necessary actions:

import { createAction, props } from '@ngrx/store';

export const loadData = createAction('[Data] Load Data');
export const loadDataSuccess = createAction(
  '[Data] Load Data Success',
  props<{ data: any[] }>()
);
export const loadDataFailure = createAction(
  '[Data] Load Data Failure',
  props<{ error: any }>()
);

Now let's create a simple effect:

import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { map, mergeMap, catchError } from 'rxjs/operators';
import { DataService } from '../services/data.service';
import * as DataActions from '../actions/data.actions';

@Injectable()
export class DataEffects {
  loadData$ = createEffect(() =>
  this.actions$.pipe(
    ofType(DataActions.loadData),
    mergeMap(() =>
      this.dataService.fetchData().pipe(
        map((data) => DataActions.loadDataSuccess({ data })),
        catchError((error) => of(DataActions.loadDataFailure({ error })))
      )
    )
  )
);

  constructor(private actions$: Actions, private dataService: DataService) {}
}

In the example above, we've used createEffect to create an effect that listens for the loadData action. When this action is dispatched, the effect triggers the dataService.fetchData() method and, upon receiving the data, dispatches the loadDataSuccess action with the fetched data.

  1. Retrieving Data from the Store

In some cases, you might need to retrieve data from the store to be used in the effect. To do this, we can use the store.select() method to get the required data.

Let's modify the previous example by adding a userId parameter to the fetchData method:

// data.actions.ts
export const loadData = createAction(
  '[Data] Load Data',
  props<{ userId: number }>()
);

// data.effects.ts
loadData$ = createEffect(() =>
  this.actions$.pipe(
    ofType(DataActions.loadData),
    withLatestFrom(this.store.select(selectUserId)),
    mergeMap(([action, userId]) =>
      this.dataService.fetchData(userId).pipe(
        map((data) => DataActions.loadDataSuccess({ data })),
        catchError((error) => of(DataActions.loadDataFailure({ error })))
      )
    )
  )
);

constructor(
  private actions$: Actions,
  private store: Store,
  private dataService: DataService
) {}

In this example, we've added the withLatestFrom operator to combine the latest value from the selectUserId selector with the loadData action. We then pass the userId parameter to the fetchData method.

  1. Combining Multiple Actions or HTTP Requests

Sometimes, you might need to combine multiple actions or HTTP requests in a single effect. To achieve this, we can use the forkJoin or zip operators from RxJS.

Let's create an effect that fetches data from two different API endpoints and combines the results before dispatching a single action.

First, update the loadDataSuccess action to accept two data properties:

// data.actions.ts
export const loadDataSuccess = createAction(
  '[Data] Load Data Success',
  props<{ data1: any[]; data2: any[] }>()
);

Now, let's create an effect that combines the results of two HTTP requests:

// data.effects.ts
import { forkJoin } from 'rxjs';

loadData$ = createEffect(() =>
  this.actions$.pipe(
    ofType(DataActions.loadData),
    withLatestFrom(this.store.select(selectUserId)),
    mergeMap(([action, userId]) =>
      forkJoin([
        this.dataService.fetchData1(userId),
        this.dataService.fetchData2(userId),
      ]).pipe(
        map(([data1, data2]) =>
          DataActions.loadDataSuccess({ data1, data2 })
        ),
        catchError((error) => of(DataActions.loadDataFailure({ error })))
      )
    )
  )
);

constructor(
  private actions$: Actions,
  private store: Store,
  private dataService: DataService
) {}

In this example, we've used the forkJoin operator to make two HTTP requests simultaneously using dataService.fetchData1() and dataService.fetchData2() methods. Once both requests are completed, the effect combines the results into a single loadDataSuccess action.

Conclusion

NgRx Effects are an essential part of state management in Angular applications, enabling you to manage side effects and asynchronous tasks seamlessly. In this blog post, we covered three different levels of complexity in NgRx Effects, from a basic example to more advanced scenarios involving data retrieval from the store and combining multiple actions or HTTP requests.

Understanding these concepts will help you leverage the full power of NgRx and build more robust, maintainable, and scalable Angular applications.

If you're looking for professional assistance with your NGRX projects, don't hesitate to reach out to the experts at bmdevservices.com. They can provide you with invaluable guidance and support to ensure your project's success.