Switching to the Most Recent Observable with switchMap
Aug 30, 2019 • 8 Minute Read
Introduction
In this guide, we’ll be learning about the switchMap operator. The switchMap operator maps each value to an observable, then it flattens all of the inner observables. It basically projects each source value to an observable which is then merged in the output observable, emitting values only from the most recently projected observable.
Syntax of switchMap Operator
switchMap<T, R, O extends ObservableInput >(project: (value: T, index: number) => O, resultSelector?: (outerValue: T, innerValue: ObservedValueOf , outerIndex: number, innerIndex: number) => R): OperatorFunction<T, ObservedValueOf | R>
The parameter project in the syntax is a function that, when applied to an item emitted by the source observable, returns an observable. The parameter resultSelector is optional and the default value of this parameter is undefined. The return value, “OperatorFunction<T, ObservedValueOf | R>”, is an observable that emits the result of applying the projection function (and the optional deprecated resultSelector) to each item emitted by the source observable and taking only the values from the most recently projected inner observable.
Using the switchMap Operator in Angular
The switchMap operator returns an observable that emits items based on applying a function that you supply to each item emitted by the source observable where that function returns an inner observable. Each time it observes one of these inner observables, the output observable begins emitting the items emitted by that inner observable.
When a new inner observable is emitted, switchMap stops emitting items from the previously-emitted inner observable and begins emitting items from the new one. It continues to behave like this for subsequent inner observables.
import { of } from 'rxjs';
import { switchMap } from 'rxjs/operators';
const switched = of(1, 2, 3).pipe(switchMap((x: number) => of(x, x * 2, x * 3)));
switched.subscribe(x => console.log(x));
// outputs
// 1
// 1
// 1
// 2
// 4
// 6
// ... and so, on
As we can see in the above example, by using the switchMap operator, we are logging the values by switching to the recent observable using the switchMap operator and emitting the value from that observer. Let’s see another example where we are returning an interval observable on every click event. After every click, we are logging value after a time interval.
import { fromEvent, interval } from 'rxjs';
import { switchMap } from 'rxjs/operators';
const clicks = fromEvent(document, 'click');
const mapResult = clicks.pipe(switchMap((ev) => interval(1000)));
mapResult.subscribe(x => console.log(x));
Using the switchMap Operator in Routing
Let’s now look at how we can use the switchMap operator in Angular routing. We can use the switchMap operator with Activated Route.
First, import the Router, ActivatedRoute, and ParamMap tokens from the router package. Then, import the switchMap operator because you will need it later to process the observable route parameters. As usual, you’ll write a constructor that asks Angular to inject services that the component requires and reference them as private variables.
Later, in the ngOnInit method, you’ll use the ActivatedRoute service to retrieve the parameters for the route, pull the employee ID from the parameters, and retrieve the employee to display. The paramMap processing is a bit tricky. When the map changes, you’ll get() the ID parameter from the changed parameters. You might think now is the time to use the RxJS map operator but the EmployeeService operator returns an Observable . So, you flatten the observable with the switchMap operator instead. The switchMap operator also cancels previous in-flight requests. If the user re-navigates to this route with a new ID while the EmployeeService is still retrieving the old ID, switchMap discards the old request and returns the employee for the new ID.
Filename: employe-detail.component.ts
import { switchMap } from 'rxjs/operators';
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { Observable } from 'rxjs';
import { EmployeeService } from '../employee.service';
import { Employee} from '../employee;
@Component({
selector: 'app-employee-detail',
templateUrl: './employee-detail.component.html',
styleUrls: ['./employee-detail.component.css']
})
export class EmployeeDetailComponent implements OnInit {
employee$: Observable<Hero>;
constructor(
private route: ActivatedRoute,
private router: Router,
private service: EmployeeService
) {}
ngOnInit() {
this.employee$ = this.route.paramMap.pipe(
switchMap((params: ParamMap) =>
this.service.getEmployee(params.get('id')))
);
}
}
}
Let’s look at another example of the search functionality of employees. By exploring this example, we will be able to understand the characteristics of the switchMap() operator.
Filename: search.component.html
<input (keyup)="search($event.target.value)" id="name" placeholder="Search"/>
<ul>
<li *ngFor="let employee of employee$ | async">
<b>{{employee.name}} v.{{employee.address}}</b> -
<i>{{employee.description}}</i>
</li>
</ul>
The (keyup) event binding sends every keystroke to the component's search() method. Sending a request for every keystroke could be expensive. It's better to wait until the user stops typing and then send a request. That's easy to implement with RxJS operators, as shown in this excerpt.
Filename: search.component.ts
withRefresh = false;
employees$: Observable<NpmEmployeeInfo[]>;
private searchText$ = new Subject<string>();
search(employeeName: string) {
this.searchText$.next(employeeName);
}
ngOnInit() {
this.employees$ = this.searchText$.pipe(
debounceTime(500),
distinctUntilChanged(),
switchMap(employeeName =>
this.searchService.search(employeeName, this.withRefresh))
);
}
constructor(private searchService: EmployeeSearchService) { }
The searchText$ is the sequence of search-box values coming from the user. It's defined as an RxJS Subject, which means it is a multicasting observable that can also produce values for itself by calling next(value), as happens in the search() method. Rather than forward every searchText value directly to the injected EmployeeSearchService, the code in ngOnInit() pipes search values through three operators:
- debounceTime(500) - wait for the user to stop typing (1/2 second in this case).
- distinctUntilChanged() - wait until the search text changes.
- switchMap() - send the search request to the service.
The switchMap() operator has three important characteristics.
- It takes a function argument that returns an observable. EmployeeSearchService.search returns an observable, as other data service methods do.
- If a previous search request is still in-flight (as when the connection is poor), it cancels that request and sends a new one.
- It returns service responses in their original request order, even if the server returns them out of order.
Conclusion
In this guide, we have explored how we can use the switchMap operator to transform data in Angular. We have also seen how we can use the switchMap operator along with other operators and pipe functions in our application. You can learn more about subscribing to and unsubscribing from observables in Angular in my guide Subscribing to and Unsubscribing from Observables.