Start using ngrx/effects for this

You're probably only using ngrx/effects to handle the communication to an external source by triggering an effect with a NgRx action.

Start using ngrx/effects for this

You’re probably only using ngrx/effects to handle the communication to an external source by triggering an effect with an NgRx action. But did you know ngrx/effects can be used for more than this?

Effects

The @ngrx/effects library provides a way to isolate side effects into its own model, outside the NgRx store and the Angular components. It provides us an Observable actions which is basically a stream of all the dispatched actions, for every dispatched action it emits a new value (after every reducer has been called). It also has a RxJS operator ofType, which is used to filter actions based on their action type.

A typical effect uses the actions Observable as its source and uses the ofType operator to only perform its side effect when the corresponding action is dispatched. For instance if we would want to retrieve customers from a web service, we would need to create a getCustomers effect. The effect listens to every action that gets dispatched and when it retrieves an action with the action type [Customers Page] Get, it will make an HTTP request. Depending on the response the effect will either dispatch a GetCustomersSuccess action, if the request was successful, or a GetCustomersFailed action, if the request was failed. In order to retrieve the customers within the application, we have to dispatch the GetCustomers action. Inside our component where we want to show a list of all the customers, we have to use a selector to select all the customers from the store state.

// to define an effect, we use the @Effect decorator
@Effect()
getCustomers = this.actions.pipe(
  // filter out the actions, except `[Customers Page] Get`
  ofType(CustomerActionTypes.Get),
  switchMap(() =>
    // call the service
    this.service.get().pipe(
      // return a Success action when everything went OK
      map(customers => new GetCustomersSuccess(customers)),
      // return a Failed action when something went wrong
      catchError(error => of(new `GetCustomersFailed`(error))),
    ),
  ),
);

1. External sources

While the actions Observable is the most known and the most used source for your effects, it is not the only one. In fact we can use every Observable as a source.

Using RxJS Observables

@Effect()
ping = interval(1000)_._pipe(mapTo(new Ping()));

Using the JavaScript API with RxJS

@Effect()
online = merge(
  of(navigator.onLine),
  fromEvent(window, 'online').pipe(mapTo(true)),
  fromEvent(window, 'offline').pipe(mapTo(false)),
).pipe(map(online => online ? new IsOnline() : new IsOffline()));

Using the Angular Material CDK

@Effect()
breakpoint = this.breakpointObserver
  .observe([Breakpoints.HandsetLandscape])
  .pipe(
    map(result => result.matches
      ? new ChangedToLandscape()
      : new ChangedToPortrait())
  );

2. Handling the flow of a (Angular Material) dialog

Instead of handling a dialog inside a component, it is possible to use an effect. The effect handles when to open and close the dialog and it dispatches an action with the dialog result.

@Effect()
openDialog = this.actions.pipe(
  ofType(LoginActionTypes.OpenLoginDialog),
  exhaustMap(_ => {
    let dialogRef = this.dialog.open(LoginDialog);
    return dialogRef.afterClosed();
  }),
  map((result: any) => {
    if (result === undefined) {
      return new CloseDialog();
    }
    return new LoginDialogSuccess(result);
  }),
);

3. Showing notifications

Just like the dialog example, I like to handle my notifications within an effect. Doing this keeps the rest of your application pure and more understandable in my opinion. In the example below we’ll be using the Angular Material Snackbar but the same can be applied to any other notification system.

@Effect({ dispatch: false })
reminder = this.actions.pipe(
  ofType<Reminder>(ActionTypes.Reminder),
  map(({ payload }) => {
    this.snackBar.openFromComponent(ReminderComponent, {
      data: payload,
    });
  })
)

Or if there is some kind of error:

@Effect({ dispatch: false })
error = this.actions.pipe(
 ofType<ServerError>(ActionTypes.ServerError),
 map(({ payload }) => {
   this.snackBar.open(payload.message, 'Close');
 })
)

4. Using a selector inside your effects

There are some times were you would need to access some store state inside your effect. For this, we can the use the RxJS withLatestFrom operator in combination with a selector to retrieve a slice of the store state.

@Effect()
shipOrder = this.actions.pipe(
  ofType<ShipOrder>(ActionTypes.ShipOrder),
  map(action => action.payload),
  concatMap(action =>
    of(action).pipe(
      withLatestFrom(store.pipe(select(getUserName)))
    )
  ),
  map([payload, username] => {
    ...
  })
)

To take it a step further, we can use the data retrieved by the selector in order to check if an entity already exists in the store. This gives us the power to block unnecessary GET requests if the entity already is stored in the store, if not we can fetch the entity.

@Effect()
getOrder = this.actions.pipe(
  ofType<GetOrder>(ActionTypes.GetOrder),
  withLatestFrom(action =>
    of(action).pipe(
      this.store.pipe(select(getOrders))
    )
  ),
  filter(([{payload}, orders]) => !!orders[payload.orderId])
  mergeMap([{payload}] => {
    ...
  })
)

5. Navigate based on actions

By injecting the Angular router into the effects it’s possible to redirect the user based on certain actions. In the example below we’re sending the user to the homepage when he or she logs out. Notice that we’re passing dispatch: false to the Effect decorator because we’re not dispatching any event. If we wouldn’t do this, we would be stuck in a infinite loop because the effect is dispatching the same action over and over again.

@Effect({ dispatch: false })
logOut = this.actions.pipe(
  ofType(ActionTypes.LogOut),
  tap([payload, username] => {
    this.router.navigate(['/']);
  })
)

6. Analytics/monitoring

Because every dispatched action emits a new value to the actions source, we can use this source in order to gain statistics of the application. For instance, we could log every dispatched action or only log the actions important to you by filtering the non-important actions with the ofType operator. In the example below we’re logging every action to Application Insights.

@Effect({ dispatch: false })
trackEvents = this.actions.pipe(
  ofType(...),
  tap(({ type, payload }) => {
    appInsights.trackEvent(type, payload);
  })
)

Conclusion

Knowing this, we can refactor some code that now lives inside our components or inside our NgRx store, into the ngrx/effects model. By doing this, it makes our components more pure and it keeps the side effects of our application separated. Resulting in code that is easier to reason about and also easier to test, in my opinion.

Now that you know for which cases you could use effects, you should also check out when to not to use effects.

Stop using ngrx/effects for that

Not found what you were looking for? Try a similar post:

Angular.Schule → 5 useful NgRx effects that don't rely on actions