RxJS Pt. 6 - Higher-order Mapping Operators

RxJS Pt. 6 - Higher-order Mapping Operators

In this article let's examine Higher-Order Observables and Higher-Order mapping operators such as switchMap.

If we look at Observables and what kind of value they provide, we can find different types.

There are types which

  • Emit primitives such as numbers or strings
  • Emit arrays - like when you use http.get method
  • Emit Observables
of(3, 7)
  .pipe(
    map(id => this.http.get<Supplier>
      (`${this.url}/${id}`)
)).subscribe()

In this example, we define a source Observable that emits two numeric ID values. Each ID is mapped to an http.get(), which returns an Observable. So, each emitted item from the source Observable emits an Observable to the output Observable. Any Observable that emits an Observable is called a Higher-Order Observable. The original source Observable is then called the outer Observable, and the Observables defined within the pipeline are called inner Observables. You will often see the calls inner and outer Observable in the RxJS documentation.

Screenshot 2022-05-27 at 18.34.20.png But there is an issue with this code. We are not subscribing to the inner Observable. To emit the supplier objects from the inner Observables, we need to subscribe to those Observables, something like this

of(3,7)
  .pipe(
    map(id => this.http.get<Supplier>
      (`${this.url}/${id}`)
)).subscribe(o=>o.subscribe())

The first subscribe subscribes to the outer Observable, which emits 3 and 7. The nested subscribe subscribes to each inner Observable, which emit the associated supply object. But this code is not too nice. How would we subscribe to the inner Observable when using an async pipe? And how would be unsubscribe? This is not a good practice, to have nested subscriptions. Another issue with this code is the return type. Instead of an Observable, that emits a value, this is an Observable, that emits an Observable. We can not bind our template to something that looks like that. What do we do instead? That is the purpose of Higher-Order mapping operators.

Let's imagine that we have the following codes

export interface Car {
  id: number
  engine?: number
  supplierIds?: number[]
}

Each of our cars has a supplierId, we can use that set of supplier IDs and retrieve each supplier for a product using http.get() method

A better choice is to leverage a higher-order mapping operator.

Higher-order Mapping Operators

Higher-order mapping operators are a family of operators with names that end with map (xxxMap()) These operators map each value from a source or outer Observable to a new inner Observable. They automatically subscribe and unsubscribe from each Observables, so we do not have to. They flatten and emit the resulting values to the output Observable.

Examples of Higher-order Mapping Operators :

  • concatMap
  • mergeMap
  • switchMap

concatMap

The concatMap operator transforms each emitted item to a new inner Observable as defined by a function we provide.

concatMap(i => of(i))

Here we map each emitted item to an Observable that emits that item. concatMap is different from the other higher-order mapping operators in that processing is serial. It waits for each inner Observable to complete before processing the next one. The items emitted from each inner Observable are concatenated onto the resulting output stream in sequence.

of('A1','A2')
    .pipe(
      concatMap(id => this.http.get<Car>(`${this.url}/${id}`))
    ).subscribe(item => console.log(item))

In this example, we use the of creation function to create an Observable containing two strings. We pipe each string through the concatMap operator to retrieve the data using the string as an ID.

sima.png

When 'A1' is emitted from the source Observable, the mapping function executes, which creates the new inner Observable. It then subscribes to that Observable and the get request is issued using the emitted string as an ID. concatMap then waits for the response. When the response arrives the data emitted from this inner Observable is concatenated to the output Observable and the inner Observable completes. The next emitted string is not processed until after the prior inner Observable completes. The mapping function then executes creating the next new inner Observable. It then subscribes to that Observable and the GET request is issued. concatMap again waits for the response. When the response is returned, the data emitted from this inner Observable is concatenated to the output Observable and the inner Observable completes.

As a result, each Car's data is emitted to the output stream in sequence. So the important notes:

  • Observables are queued
  • Only one Observable runs at a time
  • An Observable must complete before the next Observable can execute

concatMap is a transformation operator

mergeMap

Think of mergeMap as higher-order mapping plus merging. Just like the concatMap, the mergeMap operator transforms each emitted item to a new inner Observable as defined by a function we provide.

concatMap(i => of(i))

Here, we map each emitted item to an Observable the emits the item. mergeMap is different from other higher-order mapping operators in that it executes the inner Observables in parallel and merges their results.

of('C1','C2')
    .pipe(
      mergeMap(id => this.http.get<Car>(`${this.url}/${id}`))
    ).subscribe(item => console.log(item))

When the first item is emitted, it also immediately processed. As soon as the second item is emitted too, it is processed agains immediately. When a response is returned, the data emitted from the inner Observable is merged into the output Observable. Since the inner Observables are executed in parallel, they are merged into the output Observable in not particular order. As you can see, if 'A2' is processed faster, it can be seen in the result earlier.

Screenshot 2022-05-28 at 0.13.14.png So the important notes:

  • Observables working concurrently
  • Observables complete based on how quickly they finish

mergeMap is a transformation operator

switchMap

Think of switchMap as higher-order mapping plus switching.

concatMap(i => of(i))

Just like the other higher-order-operators, switchMap transforms each emitted item into a new inner Observable as defined by a function we provide. switchMap is different from other higher-order mapping operators in that it unsubscribes and stops the prior inner Observable, then switches to the new inner Observable. It only subscribes to one inner Observable at a time

of('A1','A2')
    .pipe(
      switchMap(id => this.http.get<Car>(`${this.url}/${id}`))
    ).subscribe(item => console.log(item))

Screenshot 2022-05-28 at 0.03.17.png

the switchMap subscribes to the input Observable and creates a new output Observable. When A1 is emitted, the mapping function executes, which creates the new inner Observable. It then subscribes to that Observable and the GET request is issued. When A2 is emitted, the mapping function executes, which creates a new inner Observable. switchMap then switches to that inner Observable on unsubscribing from the prior inner Observable and subscribing to the new inner Observable. In this example, it unsubscribed from he A1 inner Observable before A1 had a chance to emit. When the response is returned, the data emitted from the inner Observable is merged into the output Observable. No matter how many inner Observables are created, switchMap only subscribes to one at the time, but that does not necessarily mean that we will only have one emission. If any inner Observable completes before the next emission occurs, it will be merged into the output Observable.

However if A1 manages to complete before A2 starts, the result will be merged in the output Observable.

You could use switchMap to stop any prior Observable before switching to the next one. For example a type ahead or auto completion, or when a user selects item from a list.

switchMap is a transformation operator

Did you find this article valuable?

Support Renátó Bogár by becoming a sponsor. Any amount is appreciated!