How to Handle Side Effects in Angular Using NgRx Effects
Tags: angular, ngrx, frontend, typescript
Published: 2024-06-09 15:31:28

Side-effects! They are one of the most common tasks in our applications. In Angular, but build application if we don't take care the component ends with a lot of responsability, like get, process and render the data. But in Angular most of time when we need to get data from an API, instead of put the logic to handle everything related to HTTP requests, we create services to put the logic there, but our components still need to use these services to subscribe to them.

When we use [Ngrx,](https://ngrx.io/) the main idea is for components to trigger actions. These actions then cause the reducer to make the necessary changes in the state and get the updated data using the selectors in the component.

But how I can handle side-effect changes? For example start a http request, get the data and trigger the action with the result? who is responsible to get the data, process and update the state?

let's show a scenario, I need to show a list of players from my state, and the players come from an API. We have two actions to start this process: `Players Load` and `Player Load Success`.

```typescript
export const HomePageActions = createActionGroup({
  source: 'Home Page',
  events: {
    'Accept Terms': emptyProps(),
    'Reject Terms': emptyProps(),
    'Players Load': emptyProps(),
    'Player Loaded Success': props<{ players: Array<any> }>(),
  },
});
​
```

We have to have a separation, so we create the `players.service.ts` with the responsibility to get the data.

```typescript
import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { delay, map } from 'rxjs';
import { environment } from '../../environments/environment';
import { Player } from '../entities/player';
​
@Injectable({ providedIn: 'root' })
export class PlayersService {
  private _http = inject(HttpClient);
​
  public getPlayers() {
    return this._http
      .get<{ data: Array<Player> }>(`${environment.apiUrl}/players`, {
        headers: {
          Authorization: `${environment.token}`,
        },
      })
      .pipe(
        map((response) => response.data),
        delay(5000),
      );
  }
}
​
```

But, I can't change the `state` in the reducer because it is a function, I can't do async task or dispatch actions from there, the only place available is to use the same component to dispatch the actions when get the data.

Open the `home.component.ts`, we inject the `PlayersService`, in the `onOnInit` lifecycle dispatch the `HomePageActions.playersLoad()` action to set the loading to true, and subscribe to the `this._playersService.getPlayers()` after get the data then dispatch the `playerLoadedSuccess` action with the response.

The code looks like:

```typescript
export class HomeComponent implements OnInit {
  private _store = inject(Store);
  private _playersService = inject(PlayersService);
  public $loading = this._store.selectSignal(selectLoading);
  public $players = this._store.selectSignal(selectPlayers);
  /** 
    others properties removed to d to keep simplicity.
 **/
​
  public ngOnInit(): void {
    this._store.dispatch(HomePageActions.playersLoad());
    this._playersService.getPlayers().subscribe(players => {
      this._store.dispatch(HomePageActions.playerLoadedSuccess({
        players
      }))
    })
  }
```

The previous code works, but why does the `home.component` have to `subscribe` to the service and also dispatch the action when the data arrives? Why does the `home.component` need to know who is responsible for loading the data? The home component only needs to trigger actions and react to state changes.

This is where [NgRx Effects](https://ngrx.io/guide/effects) are useful. They take actions, perform the necessary tasks, and dispatch other actions.

## **The Effects**

What is an effect? It is a class like a service with the `@Injectable` decorator and the `Actions` injected. The [`Actions`](https://ngrx.io/api/effects/Actions) [service](https://ngrx.io/api/effects/Actions) help to listen *each* action dispatched *after* the reducer.

```typescript
​@Injectable()
export class HomeEffects {
      private _actions = inject(Actions);
}
```

We declare a field using the [`createEffe`](https://ngrx.io/api/effects/createEffect)[`ct` function](https://ngrx.io/api/effects/createEffect), any action returned from the effect stream [is then disp](https://ngrx.io/api/effects/createEffect)atch back to the [`Store`](https://ngrx.io/api/store/Store) and the [A](https://ngrx.io/api/store/Store)ctions are filtered u[sing a](https://ngrx.io/api/effects/Actions) [ofType](https://ngrx.io/api/effects/ofType) operator to takes one or more actions. The of action is then flatten and mapped into a new observable using any high-orders operator like `concatMap`, `exhaustMap` , `switchMap` or `mergeMap`.

```typescript
  loadPlayers = createEffect(() =>
    this._actions.pipe(
      ofType(HomePageActions.playersLoad)
));
```

Since [the version](https://ngrx.io/api/effects/createEffect) 15[.2 we also have](https://ngrx.io/guide/effects/operators#oftype) functional effects, instead to use a class use the same `createEffect` function to create the effects.

```typescript
export const loadPlayersEffect = createEffect(
  (actions$ = inject(Actions)) => {
});
```

But how does it work? Well, the component [triggers](https://ngrx.io/api/store/Store) the load product action, then the effect listens for this action. Next, we inject the service to get the [data](https://ngrx.io/api/effects/ofType) and [trigger an action](https://ngrx.io/guide/effects/operators#oftype) with the data. The reducer then listens for this action and makes the change.

Does it seem like too many steps? Let me show you how to refactor our code to use Effects!

## **Moving To Effects**

It's time to start using `effects` in our project. We continue with the initial [**project of NgRx**, clone it, and switch to the `action-creators`](https://www.danywalls.com/understanding-when-and-why-to-implement-ngrx-in-angular) branch.

```typescript
git clone https://github.com/danywalls/start-with-ngrx.git
git switch feature/using-selectors
```

Next, install effects `@ngrx/effects` package from the terminal.

```typescript
 npm i @ngrx/effects
```

Next, open the project with your favorite editor and create a new file [`src/app/pages/about/state/home.effects.ts`](https://www.danywalls.com/understanding-when-and-why-to-implement-ngrx-in-angular). Declare a `loadPlayersEffect` using the `createEffect` function, inject `Actions` and `PlayersService`, and then pipe the actions.

```typescript
import { inject } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { PlayersService } from '../../../services/players.service';

export const loadPlayersEffect = createEffect(
  (actions$ = inject(Actions), playersService = inject(PlayersService)) => {
    return actions$.pipe()
  )
);
```

Use `ofType` to pipe the actions and filter by the `HomePageActions.playersLoad` action type.

```typescript
  loadPlayers = createEffect(() =>
    this._actions.pipe(
      ofType(HomePageActions.playersLoad)
      )
  );
```

Use the `concatMap` operator to get the stream from the action, use the playerService and call the `getPlayers()` method and use `map` to dispatch `HomePageActions.playerLoadedSuccess({ players })`.

```typescript
concatMap(() =>
        this._playersService
          .getPlayers()
          .pipe(
            map((players) => HomePageActions.playerLoadedSuccess({ players })),
          ),
      ),
```

After the map, handle errors using the `catchError` operator. Use the `of` function to transform the error into a `HomePageActions.playerLoadFailure` action and dispatch the error message.

```typescript
catchError((error: { message: string }) =>
            of(HomePageActions.playerLoadFailure({ message: error.message })),
          ),
```

The final code looks like:

```typescript
import { inject } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { PlayersService } from '../../../services/players.service';
import { HomePageActions } from './home.actions';
import { catchError, concatMap, map, of } from 'rxjs';

export const loadPlayersEffect = createEffect(
  (actions$ = inject(Actions), playersService = inject(PlayersService)) => {
    return actions$.pipe(
      ofType(HomePageActions.playersLoad),
      concatMap(() =>
        playersService.getPlayers().pipe(
          map((players) => HomePageActions.playerLoadedSuccess({ players })),
          catchError((error: { message: string }) =>
            of(HomePageActions.playerLoadFailure({ message: error.message })),
          ),
        ),
      ),
    );
  },
  { functional: true },
);
```

We have the effect ready, its time to register it in the app.config, so import the home.effect and use the provideEffects function pass the homeEffect

The app.config looks like:

```typescript
import * as homeEffects from './pages/home/state/home.effects';

export const appConfig = {
  providers: [
    provideRouter(routes),
    provideStore({
      home: homeReducer,
    }),
    provideStoreDevtools({
      name: 'nba-app',
      maxAge: 30,
      trace: true,
      connectInZone: true,
    }),
    provideEffects(homeEffects), //provide the effects
    provideAnimationsAsync(),
    provideHttpClient(withInterceptors([authorizationInterceptor])),
  ],
};
```

We have registered the effect, so it's time to refactor the code in the `HomeComponent`. Remove the injection of the players service, as we no longer need to subscribe to the service.

The home component looks like:

```typescript
export class HomeComponent implements OnInit {
  private _store = inject(Store);
  public $loading = this._store.selectSignal(selectLoading);
  public $players = this._store.selectSignal(selectPlayers);
  public $acceptTerms = this._store.selectSignal(selectAcceptTerms);
  public $allTasksDone = this._store.selectSignal(selectAllTaskDone);
​
  public ngOnInit(): void {
    this._store.dispatch(HomePageActions.playersLoad());
  }
​
  onChange() {
    this._store.dispatch(HomePageActions.acceptTerms());
  }
​
  onRejectTerms() {
    this._store.dispatch(HomePageActions.rejectTerms());
  }
}
```

Done! Our app is now using effects, and our components are clean and organized!

## **Recap**

We learned how to handle side-effects like HTTP requests and clean up components that have too many responsibilities. By using actions, reducers, and effects to manage state and side-effects. We can refactor our component to use NgRx Effects for fetching data from an API. By moving the data-fetching logic to effects, components only need to dispatch actions and react to state changes, resulting in cleaner and more maintainable code.

* Source Code: [https://github.com/danywalls/start-with-ngrx/tree/using-effects](https://github.com/danywalls/start-with-ngrx/tree/using-effects)
    
* [NgRx Effects Documentation](https://ngrx.io/guide/effects)
    
* [NgRx createEffect](https://ngrx.io/api/effects/createEffect)
    
* [ofType](https://ngrx.io/guide/effects/operators#oftype)