Angular + switchMap Example

By Arvind Rai, November 17, 2018
This page will walk through Angular and RxJS switchMap operator Example. RxJS switchMap emits Observable after applying the given function to each item emitted by source Observable. The Observable emitted by given function that is also called inner Observable, is returned by switchMap operator. switchMap starts emitting items emitted by inner Observable. When a new inner Observable is emitted, the switchMap stops emitting items from previous inner Observable and starts emitting items from latest inner Observable. It continues to do in the same way for all subsequent inner Observable. switchMap is a RxJS pipeable operator and it is used within pipe function of Observable from RxJS 6. switchMap is imported from rxjs/operators as following.
import { switchMap } from 'rxjs/operators'; 
We can use debounceTime with switchMap to delay the emitting of values from source Observable for the given amount of time. The scenarios to use switchMap are search functionality, accessing URL parameter etc.

Technologies Used

Find the technologies being used in our example.
1. Angular 7.0.0
2. Angular CLI 7.0.3
3. TypeScript 3.1.1
4. Node.js 10.3.0
5. NPM 6.1.0
6. RxJS 6.3.3
7. In-Memory Web API 0.6.1

Using switchMap in Search Operation

Here we will discuss using switchMap in search functionality. In search operation, user may frequently change search keyword. So it is important that we should get result only with latest search keyword. For any keyword switchMap will emit items from inner Observable and if before completion a new search keyword is hit, then switchMap will stop emitting previous inner Observable and start emitting the latest inner Observable. Hence user always gets the result for latest search keyword. switchMap is used as following.
searchBook() {
  this.bookId.valueChanges.pipe(
     switchMap(id => {
       console.log(id);
       return this.bookService.getBook(id);
     })
  ).subscribe(res => this.book = res);
} 
Using switchMap with debounceTime:
debounceTime delays the emit of new value from source Observable for the given time after an emit from source Observable has taken place. If within the delay due time, more than one values arrived, then debounceTime keeps track of most recent value and emits the latest one from source Observable once due time is over. Find the code snippet for switchMap with debounceTime.
searchBook() {
  this.bookId.valueChanges.pipe(
     debounceTime(500),
     switchMap(id => {
       console.log(id);
       return this.bookService.getBook(id);
     })
  ).subscribe(res => this.book = res);
} 
The advantage of using debounceTime is that switchMap does not get every search keyword which has been entered and changed within the given delay time by the user because debounceTime emits only the latest value from source Observable only after the given delay time is over. Hence some useless processing by switchMap is avoided. In the above example once the value from source Observable is emitted by debounceTime then only after 500 milliseconds, the latest value from source Observable will be emitted to switchMap operator.

Error handling with switchMap:
We can use catchError with switchMap to handle any error as following.
searchBook() {
  this.bookId.valueChanges.pipe(
    debounceTime(500),
    switchMap(id => {
       console.log(id);
       return this.bookService.getBook(id);
    }),
    catchError(err => of(null))
  ).subscribe(res => this.book = res);
} 

Using switchMap to Access URL Parameter in Routing

To access URL query parameter, we use switchMap operator in routing. Suppose there are many links and on the click of the link, detail is shown. If the user clicks more than one link in a very short span of time then it is necessary that detail should be shown of only latest clicked link. As we know that switchMap stops emitting values of inner Observable if new inner Observable starts emitting. Find the code snippet to use switchMap to access parameter in angular routing.
ngOnInit() {
  this.route.params.pipe(
	switchMap((params: Params) => this.bookService.getBook(+params['id']))
  )
  .subscribe(book => this.book = book);
} 

Complete Example

book.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable, of, pipe } from 'rxjs';
import { switchMap, debounceTime, catchError } from 'rxjs/operators';

import { BookService } from './book.service';
import { Book } from './book';
import { FormControl, FormBuilder, FormGroup } from '@angular/forms';

@Component({
   selector: 'app-book',
   template: `
    <h3>Search Book</h3>
    <form [formGroup]="bookForm">
      ID: <input formControlName="bookId">
    </form>
    <br/>
    <div *ngIf="book">
      Id: {{book.id}}, Name: {{book.name}}, Category: {{book.category}}
    </div>
   `
})
export class BookComponent implements OnInit { 
   book: Book;
   constructor(private bookService: BookService, private formBuilder: FormBuilder) { }
   ngOnInit() {
      this.searchBook();
   }
   bookId = new FormControl(); 
   bookForm: FormGroup = this.formBuilder.group({
      bookId: this.bookId
     }
   );
   searchBook() {
    this.bookId.valueChanges.pipe(
      debounceTime(500),
      switchMap(id => {
        console.log(id);
        return this.bookService.getBook(id);
      })
    ).subscribe(res => this.book = res);
   }
} 
fav-book.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { switchMap } from 'rxjs/operators';

import { Book } from './book';
import { BookService } from './book.service';

@Component({
   template: `
    <div *ngIf="book">
      Id: {{book.id}}, Name: {{book.name}}, Category: {{book.category}}
    </div>
   `
})
export class FavBookComponent implements OnInit { 
   book: Book; 
   constructor(private bookService: BookService, private route: ActivatedRoute) { } 
   ngOnInit() {
      this.route.params.pipe(
        switchMap((params: Params) => this.bookService.getBook(+params['id']))
      )
      .subscribe(book => this.book = book);
   }
} 
book.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { Book } from './book';
import { catchError } from 'rxjs/operators';

@Injectable({
    providedIn: 'root'
})
export class BookService {
    bookUrl = "/api/books";	

    constructor(private http: HttpClient) { }
    
    getBook(id: number): Observable<Book> {
      return this.http.get<Book>(this.bookUrl + "/" + id).pipe(
          catchError(err => of(null))
      );
    }    
} 
book.ts
export interface Book {
   id: number;
   name: string;
   category: string;
} 
app.component.ts
import { Component } from '@angular/core';

@Component({
   selector: 'app-root',
   template: `
    <app-book></app-book>
    <br/><br/>
    <a [routerLink]="['/my-book', 101]">My Favourite Book</a>
    <router-outlet></router-outlet>	
   `
})
export class AppComponent { 
} 
test-data.ts
import { InMemoryDbService } from 'angular-in-memory-web-api';

export class TestData implements InMemoryDbService {
  createDb() {
    let bookDetails = [
      { id: 101, name: 'Angular by Krishna', category: 'Angular' },
      { id: 102, name: 'Core Java by Vishnu', category: 'Java' },
      { id: 103, name: 'NgRx by Rama', category: 'Angular' }
    ];
    return { books: bookDetails };
  }
} 
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule }   from '@angular/router';

import { AppComponent }  from './app.component';
import { BookComponent }  from './book.component';
import { FavBookComponent }  from './fav-book.component';

//For InMemory testing
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { TestData } from './test-data';

@NgModule({
  imports: [     
      BrowserModule,
      HttpClientModule,
      FormsModule,
      ReactiveFormsModule,
      RouterModule.forRoot([
        {
          path: 'my-book/:id',
          component: FavBookComponent
        }
      ]),
      InMemoryWebApiModule.forRoot(TestData)		
  ],
  declarations: [
      AppComponent,
      BookComponent,
      FavBookComponent
  ],
  providers: [
  ],
  bootstrap: [
      AppComponent
  ]
})
export class AppModule { } 

Run Application

To run the application, find the steps.
1. Download source code using download link given below on this page.
2. Use downloaded src in your Angular CLI application. To install Angular CLI, find the link.
3. Install angular-in-memory-web-api@0.6.1
4. Run ng serve using command prompt.
5. Access the URL http://localhost:4200
Find the print screen of the output.
Angular + switchMap Example

References

RxJS Observable
Angular: The RxJS library
Pipeable Operators

Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI









©2023 concretepage.com | Privacy Policy | Contact Us