Suppose I have a component which displays a list of actors from a movie. The component dispatches an action upon initialisation to get the actors list.
actors.component.ts
@Input() filter; // some filter applied to the data, like display only male actors for example
ngOnInit() {
this.store.dispatch(getActors({movieId: this.movieId}));
}
As you can see, this component has a filter that will be applied to the data.
The API URL looks something like this: /movies/movieId/actors
Now I can have multiple components on one page. For simplicity, lets assume we have 3 componets: C1, C2 and C3.
C1 and C2 both need the data from the same endpoint: /movies/1/actors, but each component filters the data in a different way. C1 shows only male actors and C2 only female actors.
C3 makes a request to /movies/2/actors (as you can see, it has a different movieId compared to C1 and C2).
The problem is that, in this case, 3 request will be made:
/movies/1/actors
/movies/1/actors
/movies/2/actors
As you can see, there is one request being duplicated and this is the problem. In the end, I want only 2 request being made:
/movies/1/actors
/movies/2/actors
This is the corresponding effects class:
actors.effects.ts
getActors$ = createEffect(() => this.actions$.pipe(
ofType(getActors),
mergeMap((payload) => this.actorsService.getAll({movieId: payload.movieId})
.pipe(
map(actors => ({ type: '[Actors API] Actors Loaded Success', payload: actors })),
catchError(() => EMPTY)
))
)
);
There is also a reducer which saves the data in the store in the following form:
{
movieActors: [
{movieId: 1: actors: [...]}
]
}
Again, the problem is how to prevent the same request being made twice.
One solution is to check the store if the data is already present. So the effects class will look something like this:
getActors$ = createEffect(() => this.actions$.pipe(
ofType(getActors),
withLatestFrom(action =>
of(action).pipe(
this.store.pipe(select(selectMovieActors(movieId: {action.movieId})))
)
),
filter(([{payload}, actors]) => !!actors
mergeMap((payload) => ...
From the example above, C1 and C2 will dispatch the getActors action approximately at the same time. The effect will run for C1 and make the request, then the same effect will run for C2 immediately and a new request will be made because the data from the first request hasn't arrived. This is why I can't use this solution.
Another solution is to use exhaustMap:
getActors$ = createEffect(() => this.actions$.pipe(
ofType(getActors),
exhaustMap((payload) => this.actorsService.getAll({movieId: payload.movieId})
.pipe(
map(actors => ({ type: '[Actors API] Actors Loaded Success', payload: actors })),
catchError(() => EMPTY)
))
)
);
If I were to use this solution, then only one request will be made: /movies/1/actors. The second request: /movies/2/actors will be dropped.
I have searched for solutions, but they don't seem to apply to my particular case. I want to prevent a request only if there was another request made, but with the same parameter (movieId) in my case. If there was a request to the same endpoint, but with a different id, then the new request should be made.
Edit Forgot to mention, each component has a refresh button that will dispatch the getActors({movieId: this.movieId}) action again.
With your solution 2 you can add another check for unique id with distinct()
getActors$ = createEffect(() => this.actions$.pipe(
ofType(getActors),
withLatestFrom(action =>
of(action).pipe(
this.store.pipe(select(selectMovieActors(movieId: {action.movieId})))
)
),
filter(([{payload}, actors]) => !!actors
distinct(payload => payload.movieId)
mergeMap((payload) => ...