Peter Nixey
Peter Nixey

Reputation: 16565

How can I flatten this nested observable?

I have an observable that is doing its job and working fine. However it has several layers of nesting that doesn't feel very RxJS'y and seem like they could be flattened

const filteredEntries$ = this.filterForm.controls.day.valueChanges.pipe(
  switchMap((selectedDayId: string) =>
    this.entries$.pipe(
      map((entries: Entry[]) =>
        this.entriesForDay$(entries, selectedDayId)
      )
    )
  )
)

I'd like to remove the inner nested pipe so that things stay clearer:

const filteredEntries$ = this.filterForm.controls.day.valueChanges.pipe(
  switchMap((selectedDayId: string) =>
    this.entries$
  ),
  map((entries: Entry[]) =>
    // selectedDayId is not available in this scope :( 🛑
    this.entriesForDay$(entries, selectedDayId)
  )
)

Is it possible to flatten the observable out like this and if so how can I pass the value of selectedDayId into the final .map statement?

Plot twist...

There's nothing like writing a SO question to make you answer it. I guess the solution is that you pass the selectedDayId on and return it from the first switch statement. So my question then becomes how can you do that elegantly rather than fumbling around with arrays or objects. Is there some way to mass assign the return values directly into the next method's parameters (as shown below):

const filteredEntries$ = this.filterForm.controls.day.valueChanges.pipe(
  switchMap((selectedDayId: string) =>
    this.entries$, selectedDayId // is there a way to return two values 
                                 // directly to the next method?
  ),
  map((entries: Entry[], selectedDayId) =>
    this.entriesForDay$(entries, selectedDayId)
  )
)

Upvotes: 1

Views: 275

Answers (2)

frido
frido

Reputation: 14089

You don't need switchMap in your case. You could combine the valueChanges and entries$ streams.

const filteredEntries$ = combineLatest([
  this.filterForm.controls.day.valueChanges,
  this.entries$
]).pipe(
  map(([selectedDayId, entries]) =>
    this.entriesForDay$(entries, selectedDayId)
  )
)

In general I think having one nested layer is perfectly fine. If the nesting becomes to deep I would suggest moving some of the code into extra functions.

const filteredEntries$ = this.filterForm.controls.day.valueChanges.pipe(
  switchMap((selectedDayId: string) => this.entriesFor$(selectedDayId))
)

const entriesFor$ = (selectedDayId: string) => this.entries$.pipe(
  map((entries: Entry[]) => this.entriesForDay$(entries, selectedDayId))
)

Upvotes: 1

Tal Ohana
Tal Ohana

Reputation: 1138

One possible solution is to use combineLatest:

When any observable emits a value, emit the last emitted value from each

Your code would be:

const filteredEntries$ = this.filterForm.controls.day.valueChanges.pipe(
  switchMap((selectedDayId: string) =>
    combineLatest(this.entries$, of(selectedDayId))
  ),
  map((entries: Entry[], selectedDayId) =>
    this.entriesForDay$(entries, selectedDayId)
  )
)

Upvotes: 1

Related Questions