Search code examples
angularrxjsobservablengrxngrx-store

RXJS operator for combineLatest with no null values


In my Angular project, I am trying to get the values of a few ngrx selectors, and use them all to create a query string to use for an API call.

I want to wait until they all emit a non nullish value, and then emit only once (so the api call is not sent multiple times if the values change over time). This is the solution I got to:

const selectorA$ = this.store.select(...)
const selectorB$ = this.store.select(...)
const selectorC$ = this.store.select(...)

return combineLates([selectorA$, selectorB$, selectorC$]).pipe(
   first(list => list.every(item => item != null)),
   map(([selectorA, selectorB, selectorC]) => //... return query string using the store values)
)

I am wondering if this is a right approach or is there a built in rxjs operator that can give me this behavior?

Thanks


Solution

  • Never do that:

    const selectorA$ = this.store.select(...)
    const selectorB$ = this.store.select(...)
    const selectorC$ = this.store.select(...)
    
    return combineLatest([selectorA$, selectorB$, selectorC$]).pipe(...)
    

    Imagine you've got an action dispatched that affects the state (once) and all your selectors here rely on that state and the 3 of them are updated (still at once). Every time your selectors get updated your combineLatest will emit (at least after they've all emitted initially, which with ngrx store will happen straight away as you always get an emission as soon as you subscribe to the state).

    So your combineLatest will potentially trigger 3 times instead of one (after init) when there's a new state update.

    How to solve this? Create a new select that combines the 3 values you want. It'll only emit once when needed.

    export const abc = createSelector(selectorA, selectorB, selectorC, (a, b, c) => ({ a, b, c }))
    

    And replace

    const selectorA$ = this.store.select(...)
    const selectorB$ = this.store.select(...)
    const selectorC$ = this.store.select(...)
    
    return combineLatest([selectorA$, selectorB$, selectorC$]).pipe(...)
    

    With

    this.store.select(abc)