Sometimes the simplest implementation for a feature ends up creating more complexity than it saves, only shoving the complexity elsewhere. The eventual result is buggy architecture that nobody wants to touch.

Ngrx/store is an Angular library that helps to contain the complexity of individual features. One reason is that ngrx/store embraces functional programming, which restricts what can be done inside a function in order to achieve more sanity outside of it. In ngrx/store, reducers, selectors, and RxJS operators are pure functions.

Pure functions are easier to test, debug, reason about, parallelize, and combine. A function is pure if

given the same input, it always returns the same output.

it produces no side effects.

Side effects are impossible to avoid, but they are isolated in ngrx/store so that the rest of the application can be composed of pure functions.

Side Effects

When a user submits a form, we need to make a change on the server. The change on the server and response to the client is a side effect. This could be handled in the component:

It would be nice if we could simply dispatch an action inside the component when the user submits the form and handle the side effect elsewhere.

Ngrx/effects is middleware for handling side effects in ngrx/store. It listens for dispatched actions in an observable stream, performs side effects, and returns new actions either immediately or asynchronously. The returned actions get passed along to the reducer.

Being able to handle side effects in an RxJS-friendly way makes for cleaner code. After dispatching the initial action SAVE_DATA from the component you create an effects class to handle the rest:

This simplifies the job of the component to only dispatching actions and subscribing to observables.

Ngrx/effects is easy to abuse

Ngrx/effects is a very powerful solution, so it is easy to abuse. Here are some common anti-patterns of ngrx/store that Ngrx/effects makes easy:

1. Duplicate/derived state

Let’s say you’re working on some kind of media playing app, and you have these properties in your state tree:

Because audio is a type of media, whenever audioPlaying is true, mediaPlaying must also be true. So here’s the question: “How do I make sure mediaPlaying is updated whenever audioPlaying is updated?”

Incorrect answer: Use an effect!

Correct answer: If the state of mediaPlaying is completely predicted by another part of the state tree, then it isn’t true state. It’s derived state. That belongs in a selector, not in the store.

Now our state can stay clean and normalized, and we’re not using ngrx/effects for something that isn’t a side effect.

2. Coupling actions and reducers

Imagine you have these properties in your state tree:

Then the user deletes an item. When the delete request returns, the action DELETE_ITEM_SUCCESS is dispatched to update our app state. In the items reducer the item is removed from the items object. But if that item id was in the favoriteItems array, the item it’s referring to will be missing. So the question is, how can I make sure the id is removed from favoriteItems whenever the DELETE_ITEM_SUCCESS action is dispatched?

Incorrect answer: Use an Effect!

So now we will have two actions dispatched back-to-back, and two reducers returning new states back-to-back.

Correct answer: DELETE_ITEM_SUCCESS can be handled by both the items reducer and the favoriteItems reducer.

The purpose of actions is to decouple what happened from how state is supposed to change. What happened was DELETE_ITEM_SUCCESS . It is the job of the reducers to cause the appropriate state change.

Removing an id from favoriteItems is not a side effect of deleting the item. The whole process is completely synchronous and can be handled by the reducers. Ngrx/effects is not needed.

3. Fetching data for a component

Your component needs data from the store, but the data needs to be fetched from the server first. The question is, how can we get the data into the store so the component can select it?

Painful answer: Use an effect!

In the component we trigger the request by dispatching an action:

In the effects class we listen for GET_USERS :

Now let’s say a user decides it’s taking too long for the users route to load, so they navigate away. To be efficient and not load useless data, we want to cancel that request. When the component is destroyed, we will unsubscribe from the request by dispatching an action:

In the effects class we listen for both actions now:

Okay. Now another developer adds a component that needs the same HTTP request to be made (no assumptions should be made about other components). The component dispatches the same actions in the same places. If both components are active at the same time, the first component to initialize will trigger the HTTP request. When the second component initializes, nothing additional will happen because needUsers will be false. Great!

Then, when the first component is destroyed, it will dispatch CANCEL_GET_USERS . But the second component still needs that data. How can we prevent the request from being canceled? Maybe have a count of all the subscribers? I won’t bother exploring that, but you get the point. We start to hope there is a better way of managing these data dependencies.

Now let’s say another component comes into the picture, and it depends on data that cannot be fetched until after the users data is in the store. It could be a websocket connection for a chat, or additional information about some of the users, or whatever. We don’t know if this component will be initialized before or after the other two components subscribe to users .

The best help I found for this particular scenario is this great post. In his example, callApiY requires callApiX to have been completed already. I’ve stripped out the comments to make it look less intimidating, but feel free to read the original post to learn more:

Now add on the requirement that HTTP requests should be canceled when components are no longer interested, and it gets more complicated.