Contact

Technology

May 24, 2019

Using RxJS switchMap With Angular 7 Reactive Forms to Cancel Pending Requests

Rachel Slayter

Rachel Slayter

Using RxJS switchMap With Angular 7 Reactive Forms to Cancel Pending Requests

Angular 7 is a popular web framework that provides a rich feature set including built-in components for reactive forms and RxJS observables. The combination of these two components enables powerful real-time form validation and typeahead search capabilities with a seamless experience design.

I ran into a problem on an Angular 7 project recently that had a reactive form that called an API every time the value changed. The API returned a list of customers that matched the inputs in the form below. I will be using this customer example throughout this blog post to explain the process.

ngOnInit() { this.customerForm = this.fb.group({ name: '', address: '', city: '', state: '', zip: '' });   this.customerForm.valueChanges.subscribe(() => { this.getCustomers(); }); }   getCustomers() { this.customersService.get(this.customerForm.value).subscribe(results => { this.customers = results; }); }

For the most part, this method worked. Every time a value changed in the form, the API was called, and results were returned. However, I started to notice that sometimes results would return stale data. I also noticed that there were multiple API calls active at one time in the network tab. This was especially evident when the API was slow to return data.

withoutSwitchMap.gif.pagespeed.ce.uGeV9CnOrg

The switchMap Solution

So I began searching for a way to cancel the in-flight requests with each new request. After much digging, I learned that the RxJS operator switchMap will do just that. If you aren’t familiar with RxJS, it is a library that uses reactive programming and observable streams to handle data. SwitchMap is a flattening operator that involves two observables, an outer observable and an inner observable. According to learn RxJS:

The main difference between switchMap and other flattening operators is the cancelling effect. On each emission the previous inner observable (the result of the function you supplied) is cancelled and the new observable is subscribed. You can remember this by the phrase “switch to a new observable.”

When the outer observable emits a value, the inner observable is cancelled and a new observable is subscribed. When the inner observable is an API request, it will cancel the in-flight network request.

Other RxJS flattening operators include mergeMap (aka FlatMap) and concatMap. There are many resources online that explain the differences between the three. If you do not want to cancel in-flight requests, consider using one of these other operators.

withSwitchMap.gif.pagespeed.ce.rsDkwHa62J

How to Convert to switchMap

To convert the original code to use switchMap, follow the steps outlined below.

Step 1: Create an RxJS Subject

An RxJS Subject allows for both emitting events and subscribing to events while an Observable only allows for subscribing to events.

private customerLookup$: Subject<void> = new Subject();

It is common practice to subscribe to an observable directly because it is more concise, but in this instance, it is important to understand that this.customersService.get() returns an Observable. The result can be assigned to a variable and subscribed separately. The below example behaves the same way as the concise version (commented out).

getCustomers() { const searchParams = this.customerForm.value; // this.customersService.get(searchParams).subscribe(results => { // get and subscribe // this.customers = results; // });   const getCustomers$ = this.customersService.get(searchParams); // get observable getCustomers$.subscribe(results => { // subscribe to observable this.customers = results; });}

This will come in handy in the next step.

Step 2: Add switchMap and Subscribe to the Subject

As mentioned earlier, switchMap requires two observables. The outer observable controls the execution of the inner observable. In this case, this.customerLookup$ is the outer observable. The inner observable performs the work, which in this case is this.customersService.get().

The getCustomers$ observable is returned inside the switchMap, but the result of the observable is subscribed outside the switchMap.

this.customerLookup$.pipe(switchMap(() => { const searchParams = this.customerForm.value; // add logic before api call here return this.customersService.get(searchParams); // return getCustomers$ observable})).subscribe(results => { // subscribe to results in the outer observable this.customers = results; // add logic after api call here});

Step 3: Emit an Event to the Subject

Now that the switchMap is set up, it needs to be triggered when the form changes. Convert this.getCustomers() to this.customerLookup$.next() to emit a value to the outer observable.

this.customerForm.valueChanges.subscribe(() => { // this.getCustomers(); this.customerLookup$.next();});

Final Result

Here is the final result. Note that the code has been moved from a separate method to inside ngOnInit(). The getCustomers() method is no longer needed.

This is a simplified example. To improve performance and limit the number of API requests that get initiated, I recommend adding debounceTime and distinctUntilChanged before switchMap() in the list of operators.

private customerLookup$: Subject<void> = new Subject();   ngOnInit() { this.customerForm.valueChanges.subscribe(() => { this.customerLookup$.next(); });   this.customerLookup$ .pipe(switchMap(() => { return this.customersService.get(this.customerForm.value); })) .subscribe(results => { this.customers = results; });}

sideBySide 850width.gif.pagespeed.ce.3QE02bYl-p

As you can see, switchMap reduces the number of concurrent API calls and ensures that the results always reflect the latest data. When paired with reactive forms, it produces a powerful way to perform a typeahead search in real-time on multiple inputs.

Be sure to check out the live demo and GitHub repository for a complete example. I hope this was helpful to you. Feel free to reach out to findoutmore@credera.com if you have any questions.

Conversation Icon

Contact Us

Ready to achieve your vision? We're here to help.

We'd love to start a conversation. Fill out the form and we'll connect you with the right person.

Searching for a new career?

View job openings