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 that Effects can be used for more than this?
@ngrx/effects package provides a way to isolate side effects into its model, outside the NgRx store and the Angular components.
It has an Observable
Actions, which is a stream of all the dispatched actions to the NgRx Global Store. For every dispatched action, the
Actions Observable emits a new value (after all of the reducers are invoked).
Because we don't want to trigger all the registered Effects when an action is dispatched, we can use the
ofType operator. This acts as a filter, and will only invoke the Effect when it receives the configured action(s).
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 example, if we want to retrieve customers from a web service, we need to create an Effect.
The Effect listens to every action that gets dispatched and when it retrieves an action with the action type
[Customers Page] Loaded (
CustomerPageActions.opened), it will fetch the data over HTTP. Depending on the HTTP response, the Effect either dispatches a
[Customers Api] Load Success action (
CustomerApiActions.loadCustomersSuccess) if the request was successful or a
[Customers Api] Load Failed action (
CustomerApiActions.loadCustomersFailed) if the request has failed.
To retrieve the customers within the application, we have to dispatch the
[Customers Page] Get action (
CustomerPageActions.opened). Inside the 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 Global Store state.
Effects are not limited to be triggered by the
Actions Observable is the most known and the most used source for an Effect, we can use everything that is an Observables as the source.
When that source emits a new value, the Effect is triggered.
For example, with the
interval function we can dispatch an action every x milliseconds.
Every time when the selector emits a new value, the Effect is invoked. Then you're using this, be careful that the Effect doesn't result in a state change because you might end up in a loop.
An Effect can listen to multiple actions. This makes it easy to reuse the logic of an Effect for different actions that have the same behavior.
To listen to more than one action, simply add more actions to the
Instead of handling a dialog inside a component, I find it easier to write the flow of the dialog as an Effect. The Effect opens the dialog, and when the dialog closes it returns a corresponding action back to the Global Store.
Just like the dialog example, I like to handle my notifications within an Effect. Doing this keeps the rest of the application pure and more understandable in my opinion. In the example below, we use the Angular Material Snackbar but the same can be applied to any other notification system.
For Effects that don't result in another action (like this one), it's important to set the
dispatch option to
false, otherwise, the incoming action will be re-dispatched resulting in an infinite loop (and in this case a lot of notifications). For more info about non-dispatching Effects, see the docs.
This is useful for when you want to enhance an action with data from within the store state.
This isn't a silver bullet, it's probably easier (to write/test/read) to add the data to the action when it's dispatched.
But when that data isn't available when the action is dispatched, use the RxJS
withLatestFrom operator to retrieve the data with a selector.
To take it a step further, the data retrieved by the selector can be used to check if an entity already exists in the store. Depending on the outcome we can abort the Effect early. This gives us the power to block unnecessary GET requests if the entity is already persisted in the store, if not we can fetch the entity.
In Making your application feel faster by prefetching data with NgRx we saw how we can prefetch the details of entities with NgRx when an entity is visible or when the user hovers over a link.
With Effects, it's easier to achieve the same result but both serve a different purpose.
By using an Effect, we don't need a user interaction to prefetch the data. Instead, we chain multiple Effects together by using the result of another Effect.
I've used this approach in a calendar to fetch the data for the next week. This makes moving between weeks smoother and ensures that the initial load is fast.
To implement this behavior we need two Effects. The first Effect loads the initial set of data (the current week) when the page is loaded. The second Effect loads a different set of data (the next week) when the initial set is loaded. The second Effect does this by listening to the successful action of the first Effect.
Because every dispatched action emits a new value to the
actions source, we can use this source to gain statistics of the application. For instance, we could log every dispatched action or only log the actions important. You can filter out actions by using the
ofType operator, or another filtering operator, for example, the RxJS
By injecting the Angular router into the Effects class it’s possible to redirect the user based on certain actions. In the example below, we redirect the user to the homepage when the user logs out.
Notice that in this example, we're also using a non-dispatching Effect.
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.
Now that you know for which cases you could use effects, you should also check out when to not to use effects.
Not found what you were looking for? Try a similar post, Angular.Schule → 5 useful NgRx effects that don't rely on actions, written by Ferdinand Malcher
Please consider supporting me if have you enjoyed this post and found it useful: