Sequential observable
Angular Observables RxJS SPA Typescript Visual Studio Code

How to use ConcatMap in an Angular Application

In today’s post I will explain how to use the ConcatMap RxJS operator and how it differs from the SwitchMap operator.

Recall from one of my previous posts I explained how to use the SwitchMap operator when calling a sequence of observables.

Recall that the SwitchMap() function is applied to an observable to end a previous observable and start a new observable.

When we use SwitchMap(), the observables do not complete in the order they are declared. If the first observable is a longer running request than subsequent observables, it is likely to complete after some of the observables that are declared and run later.

Explaining the ConcatMap() Operator

To be able to run the observables in the same order as they are declared, we use the ConcatMap() operator. The ConcatMap() operator allows each observable to complete sequentially and is used in cases where we require results from observables that are run earlier to provide input to dependent observables that are run later.

To demonstrate what I mean by the ConcatMap() sequential operator, I have constructed an example where I take a sequence of Fibonacci numbers: 1..2..3..5..8..13..  and create an observable from them with the of() operator.

I then run a pipe() inner observable operator that applies the concatMap() operator to each value in the observable to transform the number into a string ‘Number {val}’, where {val} is the next number in the observable.

To output the values of the transformed values within the observable we subscribe to the inner observable to output them to the console.

The code of this scenario is shown below:

const fibonnacci_source = of(1,2,3,5,8,13);

const sample_pipe = fibonnaci_source.pipe(
    concatMap(val => of(`Number: ${val}`))
);

sample_pipe.subscribe(val =>
    console.log(`Next value: ${val}`)
);

When run, the console output is shown below:

What we have seen is a simplified example of usage for ConcatMap(). In the next section, I will go through a more complex usage that involves using the operator within a subscription.

Using ConcatMap() Operator as part of a Subscription

One other more complex and practically focused example would be running an authentication request. In this situation, the authentication request is the outer observable. If the request succeeds, then we use the user login to obtain the roles the user is a member of from an inner observable, which calls an API to retrieve the number of user roles. I will explain this in more details later.

The code for a subscription incorporating ConcatMap() is shown below:

login(userName: string, password: string) {
    var config = {
        headers: new HttpHeaders({
            'Content-Type': 'application/json',
        })
    };        

    if (this.http)
        this.loginSubscription = this.http.post<any>(
        'http://localhost/BookLoan.Identity.API/api/Users/Authenticate', 
        { "UserName": userName, "Password": password }, config)
            .pipe
            (
                concatMap(
                    (value: UserName) => {
                        this.authenticate(value);
                        let completed: boolean = false;
                        let numRoles: number = 0;
                        let obs: any = 
                          this.apiService?
                            .getUserRoles(value.username)
                            .pipe(
                               switchMap(r => {
                                 this.authenticateRole(r);
                                 console.log("getUserRoles(): user " + 
                                     value.username + 
                                     " is member of " + 
                                     (r ? r.length: 0) + 
                                     "roles.");
                                 this.loginResponse
                                   .next("login success");
                                 return r;
                               })
                             ).subscribe(val => 
                             {
                                 completed = true;
                                 numRoles = val.length;
                             });
                             if (completed) 
                                 return of(numRoles.toString() + 
                                   " roles retrieved!");
                                 return of(null); 
                    }
                ),
                tap(val => {
                    if (this.currLoggedInUserValue)
                      console.log("logged in user is:" +  
                    this.currLoggedInUserValue);
                }),
                catchError((error) => { 
                    console.error('There was an error!', error); 
                    this.loginResponse.next(error);
                    return throwError(error);
                })
            )
            .subscribe(val => {
                if (val)
                    console.log(`Calling HTTP concatMap: ${val}`)
            });
}

I will explain how the above code excerpt works:

The initial call is an observable that sends an HTTP request to authenticate the credentials.

The response, which is in JSON format containing the username and authentication token is piped using the ConcatMap() function, stored locally, then uses the user name parameter to execute an observable request to obtain user roles. The mapping is shown below:

concatMap((value: User) => {
    this.authenticate(value);

    let obs: any = this.apiService?
        .getUserRoles(value.username)
        .pipe(
            switchMap(r => {
                this.authenticateRole(r);
                this.loginResponse
                    .next("login success");
                return r;
            })
        ).subscribe(val => 
        {
            completed = true;
            numRoles = val.length;
        });

After the observable is executed, the result is returned as an inner observable to the outer observable:

if (completed) 
	return of(numRoles.toString() + " roles retrieved!");
return of(null); 

The purpose of ConcatMap() is to not only flatten the result of the outer observable, but to ensure subsequent calls to the two API requests:

this.loginSubscription = this.http.post<any>('http://…', 
    { "UserName": userName, "Password": password }, config)

and

this.apiService?.getUserRoles(value.username)

are in a sequential order.

When an authentication request is processed with the above observable mappings, the inner observable is executed as shown from the logging below:

The tap() operator is used to debug the logged in user that is part of our API request input and response parameters:

tap(val => {
    if (this.currLoggedInUserValue)
        console.log("logged in user is:" + this.currLoggedInUserValue);
    }),

The above is an example of how to use the RxJS ConcatMap() operator within a subscription.

Two quite common uses with observables are:

  1. Streaming data at regular intervals, such as with polled backend data. In this case, the data is processed by the outer observable in the same order that it is received.  
  2. User interface events such as keyboard events, where each key press needs processing in the order each event is created.

Unlike SwitchMap(), where inner observables are disposed of, with ConcatMap(), the inner observable is kept active until it has completed. After completion, subsequent observables are then processed in order of their creation. There is no overlapping of inner observable execution (unlike for the MergeMap() or SwitchMap() operators).

More details and examples are in the RxJS developer site.

That is all for today’s post.

I hope you found this post useful and informative.

Social media & sharing icons powered by UltimatelySocial