Sometimes in Angular, you gotta wait for multiple HTTP requests to complete before displaying a page. That's when you should turn to forkJoin.

The forkJoin operator gives you the last emitted value from any number of Observables. However, it only does that once they all complete.

So it's all or nothing with forkJoin.

Still, it's a great tool to use when you want your Angular app to complete multiple asynchronous requests before displaying the page.

In this guide, I'll show you how to use it. Or you can go straight to the source.

Remember: if you're having problems running the Angular app locally, be sure to check the README.

Please note: this guide is a part of an ongoing series of tutorials on how to create a customer relationship management (CRM) application from scratch. The GitHub code for each guide belongs to a specific branch. The master branch holds the entire application up to the current date. If you want to follow the whole series, just view the careydevelopmentcrm tag. Note that the website displays the guides in reverse chronological order so if you want to start from the beginning, go to the last page.

The Business Requirements

Your boss Smithers walks into your office with crumbs from a breakfast biscuit still clinging to his mustache.

"It's about that CRM app you're working on," he says as he takes a seat. "There's a delay when you load the Edit Activity page. It takes about a second or so before the contact and activity type dropdowns get loaded."

You're hearing him, but you can take your eyes off the biscuit crumbs.

"I need you to hold off on loading the page until all the dependent data is loaded," Smithers says. "That way, users won't be momentarily confused when they see empty dropdowns on activities they're editing."

He walks out of your office while brushing the crumbs from his mustache on to your floor.

The Problem As It Stands

Since a picture is worth a thousand words, here's what Smithers is talking about. This is what users see for a brief moment when they edit an activity:

 

Take a look at the Activity Type and Contact dropdowns. Those should be populated because the user is editing an activity. The Activity Type and Contact dropdowns, therefore, should be preselected.

Cuz those are required fields.

Well, in fact, they are preselected. Just not yet. A second or so later and the user sees this:

 

And that's exactly what Smithers wants you to stop from happening.

Instead of loading the form and then waiting for a second or so while the dropdown data loads, Smithers wants you to show the spinner until you've loaded all the supporting data.

Easy enough with forkJoin.

A Fork in the Code

So what the heck is forkJoin anyway?

It's one of those RxJS operators you can use to handle Observables.

Specifically, it's used to handle multiple Observables in a single subscription.

There are a couple of other RxJS operators you can use for that purpose: zip and combineLatest.

But forkJoin is your best bet for this task. Don't take my word for it, read the docs: "One common use case for this is if you wish to issue multiple requests on page load (or some other event) and only want to take action when a response has been received for all."

Well that's good enough for me.

But what exactly does it do?

It aggregates Observables and gives you the final value emitted by all of them.

In this case, each Observable is emitting one and only one value. So the final value is all you're looking for.

When You Come to a Fork in the Code, Take It

The code for editing (and creating) activities uses two (2) Observables.

The first one gets all activity types that get populated in the Activity Type dropdown. The second one gets all contacts that get populated in the Contact dropdown.

The application retrieves both datasets from a downstream service which in turn retrieves the data from a MongoDB database.

So the mission here is to wait until both datasets are fully returned before showing the user the page.

Get that ball rolling by declaring two (2) class scoped variables in activity-form.component.ts. Like this:

  allActivityTypes$: Observable<ActivityType[]>;
  contacts$: Observable<Contact[]>;

Since those are Observables, it's a great idea to end the variable names with a $ sign. That seems to be what the kids are doing these days.

Now elsewhere in the class you'll assign those variables like so:

  private loadActivityTypes() {
    this.allActivityTypes$ = this.activityService.fetchAllActivityTypes();
  }

...

  private loadContacts() {
    this.contacts$ = this.contactService.fetchMyContacts();
  }

But remember what you learned in Observable Stuff 101: Those assignments don't make anything happen.

Observables don't do anything until somebody subscribes to them.

And that's where forkJoin comes into play.

Start by importing that operator:

import { Observable, forkJoin } from 'rxjs';

Then, in the same part of the code where you load the data (just one method off of ngOnInit()), add this bit:

    forkJoin([
      this.allActivityTypes$,
      this.contacts$
    ]).subscribe(([allActivityTypes, contacts]) => {
      this.handleActivityTypesResponse(allActivityTypes);
      this.handleContactsResponse(contacts),
      this.showForm();
    },
      (err) => this.handleDataLoadError(err)
    );

What you've got inside the forkJoin signature is an array of Observables. It's the two objects you defined and assigned earlier.

After that, the code invokes the subscribe() method to handle both Observables. If you look carefully, you'll see it handles them in the same order they're defined in the forkJoin() method signature.

For each Observable, the code invokes a method to handle a successful response. 

And once they're both completed, the code invokes the showForm() method.

As of now, that method just flips a boolean:

  private showForm() {
    this.loading = false;
  }

Meaning that the page is no longer loading and can safely be displayed.

The HTML in the template looks like this:

<div class="absolute-center" *ngIf="loading">
  <mat-spinner [diameter]="80"></mat-spinner>
</div>
<div *ngIf="!loading" fxFlex="100" fxLayout="column" fxLayoutGap="20px" class="route-content">
...

So it shows the spinner while loading is true and it shows the form when loading is false.

But I shouldn't neglect to cover this part of the original code block:

(err) => this.handleDataLoadError(err)

Unsurprisingly, that part of the code handles errors.

But... it doesn't discriminate.

In other words, if the either of the Observables run into an error, the code ends up at that same place. And, by the way, the other subscription is cancelled in that case. That's by default.

So that handleDataLoadError() method just leaves the user with a generic "Something went wrong while loading the data" message. It doesn't say whether it had problems loading the contact data or the activity type data.

That's okay, though. Users don't need that much info about the nature of the error. And developers can just look at the log to see what, specifically, went wrong.

Now if you save that code and test it out, you'll find that it displays the page with immediately pre-populated dropdowns. There's no delay between page display and dropdown population.

It works!

Wrapping It Up

Now you know how to use forkJoin to handle all asynchronous calls before displaying a page.

Homework assignment: think about how you might use it for other reasons. Where else would you like to wait for all asynchronous operations to complete before moving forward with processing?

Also, feel free to get the code on GitHub.

Have fun!

Photo by Lisa Fotios from Pexels