Demystify RxJS Higher-Order Observable Mapping (2024)
RxJS Tutorial - Table of Contents
Overview
In this post, we will understand the core concepts of RxJS Map Operation and what problem Higher-Order Observables solves and why they are so important
RxJS Higher Order Mapping operators are very important for many common operations in reactive programming. The higher-order maping operators of RxJS: switchMap, mergeMap, concatMap and exhaustMap are among the most widely used RxJS operators we use on a daily basis.
These operator can be a bit confusing in any specific situation, and we are always wondering how these operators work and why they are so named.
Selecting the right operator is crucial since choosing the wrong operator often does not necessarily result in a broken program, but it may lead to difficult problems over time. Lets explore
RxJS Map Operator:
First, let's understand the map operators. As the names of the operators suggest, they do some sort of mapping: but what exactly is mapping? First, let's look at the marble diagram of the RxJS Map Operator.
How the Map Operator works
You can take an input stream from the maps operator (with values1,2,3) and construct a mapped-output stream that is derived from it (with values 10,20,30).
The output stream values in the bottom are obtained by using the input stream values and by applying the function: the function simply multiplies the values by 10.
Therefore the map operator deals with the mapping of input values. Next we will see an example of how we can use it to handle HTTP requests is given below.
const http$ : Observable<Books[]> = this.http.get('/api/books');
http$
.pipe(
tap(() => console.log('HTTP request executed')),
map(res => Object.values(res['payload']))
)
.subscribe(
books => console.log("books", books)
);
In above simple example, we are building an observable HTTP that makes a backend call and we're subscribing to it.
Once the observable emit the value of the HTTP response, The HTTP response wraps data into a payload property (which is a JSON entity),
we use the RxJS map operator, as mapping function to map the JSON response payload, and extract the value of the payload property.
Now we know how RxJS mapping works, let's look at higher-order mapping next.
RxJS Higher-Order Observable Mapping
In higher order mapping, we map source observable emitted value into other Observable, instead of mapping a flat value like 1 to another value like 10.! The result is an Observable higher order.
It is like any other Observable,
but its values are also observable, to whom we can subscribe separately.
This type of mapping is used very frequently in the development of applications. Let's look at a practical example of higher order mapping.
@Component({
selector: 'search-book',
templateUrl: './search-book.component.html'
})
export class SearchBookComponent implements AfterViewInit {
form: FormGroup;
books:Books;
constructor(private fb: FormBuilder) {
this.form = fb.group({
description: [books.description,
Validators.required],
category: [books.category, Validators.required],
releasedAt: [moment(), Validators.required],
longDescription: [books.longDescription,
Validators.required]
});
}
}
In above simple example, the Reactive Form gives this.form.valueChanges Observable, which emit the current forms values while the user interacts. That will be our Observable Source
Most of the time the application has a requirement to perform certain updates or make a backend request to retrieve data depending on the current selection.
In that case, we need to take the form value, and then create a second HTTP observable that executes a backend request, and then subscribe to it.
We might try to do this manually, but we would fall into nesting anti-pattern subscriptions :
this.form.valueChanges
.subscribe(
formValue => {
const httpPost$ =
this.http.put("/api/book/", formValue);
httpPost$.subscribe(
res => ... handle successful ...
err => ... handle error ...
);
}
);
As we grow complexity in real life, like placing some more features like filters etc., our code will nest quite rapidly on multiple levels, which is one of the problems we tried to avoid when using RxJS first.
This new httpPost$ Observable is the inner Observable, created in the inside of the nested code block.
Need of Higher-Order Observables
In the more efficient way to handle the nestled http subscriptions in RxJS: we want to take the form value and turn them it into an Observable. This would also generate a higher order Observable with each value corresponding to a HTTP request.
In above example, we are actually triggering the multiple operations in parallel, which is not what we want because there is no strong guarantee that the backend will handle the all request sequentially and that the last valid form value is indeed the one stored on the backend.
So let explore next how the below four different map operators can be used to flatten observables in very different scenarios.
concatMap, switchMap and mergeMap are probably going the be the most powerful and frequently used operators in real world. Its is thereby critical to understand the difference between the two in order to spend less time debugging code.
Next we will see concatMap