#Where to dispatch store actions with RxJS to refetch data based on params being changed by a user?

18 messages · Page 1 of 1 (latest)

maiden forge
#

1/2
I manage state with NgRx and handle user interaction and data (to be provided) with RxJs. Let's say I have a movie library, based on the user selecting a genre or changing a page, the data shoud refetch (by dispatching an action to the store that then handles http request in an effect).

This is how I handle the params now inside a map operator:

  moviesParams$ = combineLatest(
    this.genreSelected$, // BehaviorSubject
    this.pageSelected$, // BehaviorSubject
  ).pipe(
    distinctUntilChanged(),
    map(([genreId, page]) => {
      if (genreId && page) {
        // THE STORE ACTION BEING DISPATCH TO REFETCH
        this.moviesFacade.fetchMoviesByGenreId(
          page,genreId,
        )
      }
      return ({ genreId, page})
    }),
    shareReplay({ refCount: true, bufferSize: 1}),
  )
#

2/2
I think a more appropriate would be a use of tap operator:

  moviesParams$ = combineLatest(
    this.genreSelected$, // BehaviorSubject
    this.pageSelected$, // BehaviorSubject
  ).pipe(
    distinctUntilChanged(),
    map(([genreId, page]) => ({ genreId, page})),
    tap(({ genreId, page}) => {
        if (genreId && page) {
        // THE STORE ACTION BEING DISPATCH TO REFETCH
        this.moviesFacade.fetchMoviesByGenreId(
          page,genreId,
        )
      }
    }),
    shareReplay({ refCount: true, bufferSize: 1}),
  )

Though, I think quite a heavy logic for the tap operator as its intended to be used mainly for logging and such... I also do not like that such an important call is "hidden" somewhere in the pipe, it's not really obvious. So I can think of only subscribing as another method:

  moviesParams$ = combineLatest(
    this.genreSelected$, // BehaviorSubject
    this.pageSelected$, // BehaviorSubject
  ).pipe(
    distinctUntilChanged(),
    map(([genreId, page]) => ({ genreId, page})),
    shareReplay({ refCount: true, bufferSize: 1}),
  ).subscribe(({ genreId, page}) => {
        if (genreId && page) {
        // THE STORE ACTION BEING DISPATCH TO REFETCH
        this.moviesFacade.fetchMoviesByGenreId(
          page,genreId,
        )
      }
    })

Are there any other cleaner ways as I am not a fan of any of them? If not, which one would you prefer?

long ivy
#
moviesParams$ = combineLatest(
    this.genreSelected$, // BehaviorSubject
    this.pageSelected$, // BehaviorSubject
  ).pipe(
    distinctUntilChanged(),
    filter(([genreId, page]) => genreId && page),
    tap(([genreId, page]) => 
        this.moviesFacade.fetchMoviesByGenreId(
          page,genreId,
        )
    ),
    map(([genreId, page]) => ({genreId, page})),
    shareReplay({ refCount: true, bufferSize: 1}),
  )

I'd do just like this, hoping you're taking in account the falsiness of number 0 for your genreId and page.

granite reef
#

Out of curiosity, assuming genreSelected and pageSelected are represented in the store and updated by their correspondig actions, why not make an effect out of it:

@Effect()
onMoviesParamsChange$: Observable<Action> = this.actions$.pipe(
  ofType(MovieActions.GENRE_SELECTED, MovieActions.PAGE_SELECTED),
  withLatestFrom([selectedGenreFromStore(), selectedPageFromStore()]),
  map(([, genreId, page]) => createActionToFetchMovieByGenreAndPage(page, genreId))
);
#

☝️ My ngrx is a bit rusty, so that might need createEffect etc.

#
onMoviesParamsChange$ = createEffect(
  () => this.actions$.pipe(
    ofType(MovieActions.GENRE_SELECTED, MovieActions.PAGE_SELECTED),
    withLatestFrom([selectedGenreFromStore(), selectedPageFromStore()]),
    map(([, genreId, page]) => createActionToFetchMovieByGenreAndPage(page, genreId))
  )
);
maiden forge
granite reef
#

Exactly, it seems like behavior that is unrelated to any UI.

#

As soon as the values in the store are updated (by listening to the actions), you refetch it and bring the store in sync

#

You might need a switchMap in there I guess

#

But that should be in the other effect I think, the one that does the refetch

#

Additionally, you avoid the need for a manual subscription to handle this, instead you rely on ngrx managing the subscription here.

#

Sure u can use the async pipe and avoid a manual subscription as well, but that means:

  • Only run the synchronization code when the observable is subscribed to in the UI.
#

Which could or could not be expected.

#

If u use ngrx, it's better to think in events that occur, and react to them. Such as page selected, genre selected, etc.

#

When u think in events, it's also okay to reuse events. Like it's perfectly fine that a single event can have multiple consequences, e.g. updating the store, triggering an effect, or multiple effects.

long ivy
#

I was actually supposing that pipe was part of a more complex effect, and didn't understand the needing of returning page and genreId values.

granite reef
#

The manual subscription gave it away for me 😄