M3enjamin
M3enjamin

Reputation: 190

NgRx: Listening to state change in service rather than using an Effect

My question is: Can a service listen to state change and make an http call based on the state value, or does it have to be triggered by an effect(NgRx) ? So instead of having a service called by an effect like in the examples you usually find on the web, you'd have a service listening to a part of the store and react based on that. And thus no more effect. My question is more of a philosophical (best practices) question than a technical question because, technically, both work.

Update

Here is a concrete case to understand why I'm asking:

Context :

I have a list of elements that I receive from my backend. I can filter that list of elements. There can be more than one filter at a time. The filtering is done on the backend side.

Current implementation:

The component dispatches a FILTER_ELEMENTS action. That action is picked up by an effect that calls the http service. the service asks the backend for a list of elements based on the filter(s). Because there can be more than one filter, the effect (or the service) needs to know what are the current filters and add the new one to that list. So I added a .withLatestFrom(this.state$) in the effect and a get the current list of filters from there.

Problem:

The problem is that the state is not updated with the new filter. I could catch the FILTER_ELEMENTS action with in my reducer and update the state but that would cause several other potential problems. Which of the effect and the reducer will be called first? What if there is a backend error? my state is updated with the new filter but the list is not filtered. What about asynchornism ? what if I ask for another filter before the sever answers or before the state is updated?

This seems a little bit too messy to me.

So my question is: What about the store really is the signle source of truth ? Instead not just a "log" of what happened that is maybe or maybe not up to date. I mean that a componenent that want to add a filter can do so by dispatching a ADD_FILTER action that would update the list of filter in the state. And my service could listen to the list of filters and call the backend everytime the list changes. So the service only calls the backend when the state changes and with what we know is the latest and real state. And then dispatch a "success" action to the store with the updated list. I don't see how that would cause more potential infinite loops than the effects... After all, the effects can also cause infinite loops... So my service would in fact be an effect that listens to store changes instead of directly to actions.

Upvotes: 4

Views: 3305

Answers (1)

bryan60
bryan60

Reputation: 29355

You shouldn't do this because it explicitly violates unidirectional data flow. Asynchronous events cause actions to go an update the store, data coming out of the store does not trigger new asynchronous events that could in turn trigger new actions. This creates potential for serious bugs, like an infinite event loop, and also makes your application difficult to reason about. The effects service exists for this reason, to make sure unidirectional data flow is never violated.

It's important to remember with ngrx that an action represents the entire application state change, full stop. The effects service essentially takes an action that needs some remote resource to describe the full state change and grabs that remote resource and maps it into a new action that does represent the full state change before it ever hits the store. If you are making further state changes based on what comes out of the store, then clearly you didn't fully describe your state change in your action.

A replay of the actions that actually get into the store should produce an identical state every single time, again, this wouldn't be the case if you do other state changes based on what you get out of the store.

Edit to address additional questions:

The store is the single source of truth by design, IE, the state of the application does not update without going through the store. It is on you as the developer to enforce this. It should not be you retroactively updating it to reflect "what happened", it should be the actual change, the store is storing the state of your application.

As it stands, it seems like you want to filter the elements, then react to receiving those elements by dispatching an UPDATE_FILTERS action, this is 100% the wrong course.

The first step to fixing your issues is to make sure that your actions contain all information needed for the state change, so, if your effects service needs to access the current state, you're doing something wrong. The FILTER_ELEMENTS action should contain all of the needed filters, the effects service should NEVER be accessing current state.

The next step is to again, ensure that your actions contain all information required for the state change. Don't catch FITLER_ELEMENTS in your reducer, map into a new action, ELEMENTS_FITLERED that contains the filtered elements AND the filters and react to that and apply both state changes in the same action. In the case of an error, you catch it and map into a FILTER_ERROR action with the needed data to create that state, which may include the old filters. As for new filters coming through before the old are done, then you use switchMap to ensure the latest filters are being looked at and previous requests are ignored.

All of this is solvable in the framework, and your effects service should NOT be able to cause infinite loops, if it can, you've designed it wrong, likely by trying to access state in the effects service. But you should never be reacting to actions with new actions.

Upvotes: 5

Related Questions