Angular + RxJS - concatMap
February 02, 2024
On this page we will learn using RxJS concatMap
operator in our Angular standalone application.
1.
concatMap
projects each source value to an observable and wait for complete before sending next value. The output is merged in the order they are processed.
2. Find the operator construct from RxJS doc.
concatMap(project: (value: T, index: number) => O): OperatorFunction<T, ObservedValueOf<O>>
3.
concatMap
maps each value of the source to an observable and then flattens all these inner observables.
4. Import
concatMap
operator from 'rxjs' as given below.
import { concatMap } from 'rxjs';
Contents
1. Using concatMap
In our simple code we have three elements in the source observable that is being emitted toconcatMap
sequentially. The inner observable passed to concatMap
has two elements. These values are being mapped after 2 second wait.
of(10, 11, 12).pipe( concatMap(sl => of("a", "b").pipe( delay(2000), map(el => sl+ "-" +el) ) ) ).subscribe(res => console.log(res));
(2 second wait) 10-a 10-b (2 second wait) 11-a 11-b (2 second wait) 12-a 12-b
concatMap
is applying to given observable that is waiting for 2 second and then emit 'a' and 'b'. These elements are combined by map
and finally concatMap
returns 10-a and 10-b. As the operation for first source element, 10, the operation is complete, concatMap
accepts next source element, 11. After 2 second concatMap
will accept next source element, 12, and so on.
If we use
mergeMap
in the above code, wait for 2 second only at start and then all output will displayed at once in console.
If we use switchMap
in the above code, only the latest inner observable is returned and the output will be 12-a, 12-b.
If we use exhaustMap
in the above code, only the oldest inner observable is returned and the output will be 10-a, 10-b.
2. concatMap vs mergeMap
mergeMap
maps each value to an observable and then flattens all these inner observable using mergeAll
where as concatMap
concats all inner observables using concatAll
. The mergeAll
and concatAll
both flattens an observable-of-observables but concatAll
puts one inner observable after the other.
Find the code with
mergeMap
.
of("x", "y").pipe( mergeMap(sl => of("1", "2").pipe( delay(2000), map(el => sl + "-" + el) )) ).subscribe(res => console.log(res));
(After two second) x1 x2 y1 y2
concatMap
.
of("x", "y").pipe( concatMap(sl => of("1", "2").pipe( delay(2000), map(el => sl + "-" + el) )) ).subscribe(res => console.log(res));
(After two second) x1 x2 (After two second) y1 y2
mergeMap
when we want the output of all inner observables in one go and use concatMap
when we want the output of all inner observables one by one.
3. concatMap vs switchMap
switchMap
maps each value to an observable and flattens all these inner observables using switchAll
. If before completing previous observable, new inner observable starts to execute then switchMap
keeps only latest inner observables and discards the previous one.
Find the code.
of("Puna", "Mumbai").pipe( // outer observable switchMap(sl => of("MH").pipe( // inner observable delay(2000), map(el => sl + "-" + el) )) ).subscribe(res => console.log(res));
Mumbai-MH
switchMap
. First inner observable is for "Puna" source element and second inner observable is for "Mumbai" source element. Second inner observable will start executing before completion of first inner observable because inner observables are taking two seconds to complete. In this case switchMap
will return only latest inner observable i.e. for "Mumbai" element.
Now find the code for
concatMap
that will return flattened observable of all inner observables.
of("Puna", "Mumbai").pipe( concatMap(sl => of("MH").pipe( delay(2000), map(el => sl + "-" + el) )) ).subscribe(res => console.log(res));
Puna-MH Mumbai-MH
4. concatMap vs exhaustMap
exhaustMap
is the opposite of switchMap
operator. exhaustMap
uses exhaustAll
to flatten all inner observables.
When source observable emits more than one elements then exhaustMap
creates inner observables for each source element. If before completion of an inner observable, a new observable starts executing then that new inner observable is discarded and old inner inner observable is kept.
Find the code.
of(101, 102).pipe( exhaustMap(sl => of("abc").pipe( delay(2000), map(el => sl + "-" + el) )) ).subscribe(res => console.log(res));
101-abc
concatMap
keeps all inner observables flattened.
of(101, 102).pipe( concatMap(sl => of("abc").pipe( delay(2000), map(el => sl + "-" + el) )) ).subscribe(res => console.log(res));
101-abc 102-abc
5. concatMap vs map
map
applies the given project function to each value emitted by the source observable. If we pass an inner observable to map and when we subscribe the source observable, we get output as inner observable that need to be subscribed further.
of("Mahesh", "Shiva").pipe( map(sl => of("201").pipe( map(el => sl + "-" + el) )) ).subscribe(res => console.log(res.subscribe(v=> console.log(v))));
Mahesh-201 Shiva-201
concatMap
.
of("Mahesh", "Shiva").pipe( concatMap(sl => of("201").pipe( map(el => sl + "-" + el) )) ).subscribe(res => console.log(res));
Mahesh-201 Shiva-201
6. concatMap Example with HTTP Requests
emp.component.tsimport { Component, OnInit } from '@angular/core'; import { EmpService } from './services/emp.service'; import { CommonModule } from '@angular/common'; import { Observable, concatMap, of } from 'rxjs'; @Component({ selector: 'my-app', standalone: true, imports: [CommonModule], templateUrl: './emp.component.html' }) export class EmpComponent implements OnInit { constructor(private empService: EmpService) { } result$!: Observable<any>; ngOnInit() { this.result$ = of(101, 102).pipe( concatMap(id => this.empService.findEmpById(id)) ); } }
{{result$ | async | json}}
import { HttpClient, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable, of } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class EmpService { constructor(private http: HttpClient) { } findEmpById(id: number): Observable<any[]> { const httpParams = new HttpParams().set('id', id); return this.http.get<any[]>("/api/getEmp", { params: httpParams }); } }
import { ApplicationConfig, importProvidersFrom } from '@angular/core'; import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; import { TestData } from './test-data'; import { provideHttpClient } from '@angular/common/http'; export const APP_CONFIG: ApplicationConfig = { providers: [ provideHttpClient(), importProvidersFrom(InMemoryWebApiModule.forRoot(TestData, { delay: 2000 })) ] };
import { InMemoryDbService } from 'angular-in-memory-web-api'; export class TestData implements InMemoryDbService { createDb() { let empDetails = [ { id: '101', name: 'Mahesh' }, { id: '102', name: 'Krishn' } ]; return { getEmp: empDetails }; } }
concatMap
will create inner observable. We are fetching employee data observable in HTML template using async
pipe. When we run the application, we will see output for first employee after two seconds and then again after two seconds, output will be overridden by the second employee data.
Find the print-screen of the output.