My Angular project has an NGRX Store which holds the state for plan
. My plan
object includes an array of locations called locations
. I subscribe to an RXJS combineLatest
to watch for changes to several values including locations
. If any of the values change I perform various calculations and need to save the updated locations
to the NGRX Store.
I cannot prevent an infinite cycle whereby a value in my combineLatest
changes, calculations are performed, a new plan
is created and a new array of locations
are created named updatedLocations
(as these are both immutable), I update the values in some of the locations
, updatedPlan.locations = updatedLocations
, and finally dispatch an action to update plan
in the Store. However my combineLatest
sees that plan.locations
has changed and the whole process starts again.
A summary of the code is as follows:
private locations$: Observable<ILocation[]> = this.store.pipe(select(fromPlan.getLocations)).pipe(distinctUntilChanged());
private foo$...
private bar$...
this.locationSubscription = combineLatest(
this.locations$,
this.foo$,
this.bar$
).subscribe(
([locations, foo, bar]) => {
// Create new Locations so I can change values.
const updatedLocations = [...locations.map(l => ({ ...l }))]
// Perform calculations
// Update the Plan
const updatedPlan = { ...this.plan };
updatedPlan.locations = updatedLocations;
this.store.dispatch(new planActions.SetPlan(updatedPlan));
}
}
Any suggestions would be really appreciated.
You have this problem because you are storing derived state which is an antipattern - see 'Duplicate/derived state' at https://medium.com/@m3po22/stop-using-ngrx-effects-for-that-a6ccfe186399
Essentially, updatedLocations
is derived from other state, so you should:
locations
, foo
, bar
in your state as you do nowconst selectUpdatedLocations = createSelector(
selectLocations,
selectFoo,
selectBar,
calculateUpdatedLocations
);
function calculateUpdatedLocations(locations, foo, bar){
// Create new Locations so I can change values.
const updatedLocations = [...locations.map(l => ({ ...l }))];
return updatedLocations;
}
I suspect something similar will apply to Plan
- ie: use a selector to build up the Plan from information in the state and selectUpdatedLocations
, rather than storing the Plan itself.