livthomas
livthomas

Reputation: 1210

Is there an RxJS operator similar to withLatestFrom but with a parameter?

My Angular 5 application is based on NgRx, a state management library similar to Redux but based on RxJS.

I often need to get the latest value from the store based on the payload of the current action.

In RxJS terminology, it means that I have my main stream that constantly produce items and for each new item I need to create a side stream based on the item's value, get the latest value from this stream, and combine it with the main stream.

At the moment, I do something like this:

@Effect()
public moveCursor$: Observable<Action> = this.actions$.pipe(
  ofType<TableAction.MoveCursor>(TableActionType.MOVE_CURSOR),
  switchMap(action => this.store$.select(selectTableById(action.payload.cursor.tableId)).pipe(
    first(),
    map(table => ({action, table}))
  )),
  map(({action, table}) => {
    ...
  })
)

I know it is probably not the best solution and I am looking for something like this (which is not possible with withLatestFrom operator):

@Effect()
public moveCursor$: Observable<Action> = this.actions$.pipe(
  ofType<TableAction.MoveCursor>(TableActionType.MOVE_CURSOR),
  withLatestFrom(action => this.store$.select(selectTableById(action.payload.cursor.tableId))),
  map(([action, table]) => {
    ...
  })
)

So my question is: Is there any RxJS operator which is similar to withLatestFrom which can take a value produced by the first stream as a parameter?

Upvotes: 8

Views: 5948

Answers (4)

AtozEvo
AtozEvo

Reputation: 31

I may be a little late and you've already solved this, but...

Firstly I cant clearly see what you're trying to achieve here. What is your intented behavior?

only when your TableActionType.MOVE_CURSOR is called and with the latest value from your store

or

when your TableActionType.MOVE_CURSOR is called or latest value from your store is updated


If it is only when your TableActionType.MOVE_CURSOR is called and with the latest value from your store, using just the withLatestFrom should be good enough

@Effect()
public moveCursor$: Observable<Action> = this.actions$.pipe(
  ofType<TableAction.MoveCursor>(TableActionType.MOVE_CURSOR),
  withLatestFrom(this.store$.select(selectTableById(action.payload.cursor.tableId))),
  map(([action, table]) => {
    ...
  })
)

If its is *when your TableActionType.MOVE_CURSOR is called or latest value from your store is updated then i would use a mergeMap to combine your Actions Observable and your Latest value from the store

//Actions Observable
this.actions$.pipe(ofType<TableAction.MoveCursor>(TableActionType.MOVE_CURSOR))

//Latest Value from the store
this.store$.select(selectTableById(action.payload.cursor.tableId))

//Result:
@Effect()
public moveCursor$: Observable<Action> = mergeMap([
  this.actions$.pipe(ofType<TableAction.MoveCursor>(TableActionType.MOVE_CURSOR)), 
  this.store$.select(selectTableById(action.payload.cursor.tableId))
]).map(({action, table}) => {
  ...
});

But beware, this will cause the effect to run EVERY time the selector is updated.(which should be okay assuming your tableId is a memoized.

Upvotes: 0

reads0520
reads0520

Reputation: 716

No, as of rxjs 6 I don't believe there is such a built-in operator. I think your current solution is the most straightforward way to do it, I'm using that pattern as well, and it's important to remember to include the first() or take(1) to avoid responding to subsequent emissions of the selector.

Upvotes: -1

Kris Jobs
Kris Jobs

Reputation: 738

I finally did it...

doEffect$ = this.actions$.pipe(
    ofType<someAction>(losActionTypes.someAction),
    switchMap/mergeMap/concatMap(    // according to your logic
    action => of(action).pipe(
       withLatestFrom(this.store.pipe(select(leFancySelector)))
    )),
    tap(console.log)    // tap if you don't believe me

Upvotes: 7

cartant
cartant

Reputation: 58400

You can use mergeMap and map to combine the action with the table selected from the store:

@Effect()
public moveCursor$: Observable<Action> = this.actions$.pipe(
  ofType<TableAction.MoveCursor>(TableActionType.MOVE_CURSOR),
  mergeMap(action => this.store$
    .select(selectTableById(action.payload.cursor.tableId))
    .pipe(
      first(),
      map(table => [action, table])
    )
  ),
  map(([action, table]) => {
    ...
  })
)

And you'd need to use first - or take(1) - to ensure that the inner observable selected from the store emits only a single value - the table that's to be combined with the action.

Upvotes: 3

Related Questions