I recently stumbled upon the need to combine a few Observables into a single stream that contained the result from each stream. I had to combine the outcome from several HTTP calls that are called Observables in Angular into a single Observable.
ForkJoin Definition
In Angular, forkJoin
is an operator that allows you to wait for multiple observables to complete and then emits their latest values as an array. It allows you to combine the results of multiple HTTP requests.
After some tinkering, I came to the conclusion that forkJoin
was the right tool for the job.
forkJoin
is an RxJS operator that allows you to wait for multiple Observables to complete and then emits their latest value as an array.
Here’s what we’ll cover in this article:
- How to use forkJoin in Angular.
- Realistic examples of forkJoin.
- Basic error handling in forkJoin.
- What an operator decision tree is.
What Is ForkJoin?
As we noted earlier, forkJoin allows you to wait for multiple Observables to complete and then emit their latest values as an array.
It can be used to combine the results of multiple HTTP requests, for example, and emit the results only when all of them are complete.
How To Use ForkJoin in Angular
Here is a quick implementation of forkJoin.
...
export class App implements OnInit {
sources = [
this.http.get('https://jsonplaceholder.typicode.com/users/1'),
this.http.get('https://jsonplaceholder.typicode.com/users/2'),
this.http.get('https://jsonplaceholder.typicode.com/users/3'),
];
constructor(private http: HttpClient) {}
ngOnInit() {
forkJoin(this.sources).subscribe(console.log);
}
}
The sources
array contains three Observables created by three HTTP calls that use the http
Angular service. The forkJoin
operator takes an array of Observables, so we pass in the sources
array.
When the array is passed into forkJoin
, the operator waits for each Observable (HTTP call) to complete. Then, it combines the last value from each call into a single Observable.
Then you subscribe to the Observable and log the data in the console.
Example of ForkJoin in Angular
What I showed above works well in theory, but in real life, things tend to be a bit more complicated.
A very common case involves the need to manipulate data and handle some errors. We’ll take a look at a few examples, which you can review in Stackblitz. Once Stackblitz installs all packages, you might need to refresh the view to see the example in the console.
Manipulating Data With Forkjoin
Most of the time, the back end returns data that has to be manipulated by the front end.
When using forkJoin
, we need to remember that the forkJoin
operator “returns an Observable that emits […] an array of values in the exact same order as the passed array,” according to the RxJS documentation.
Expanding our example, we can use the map
operator to transform the emitted values.
The map
operator takes as input the array containing the latest values emitted by the three Observables that were passed to forkJoin
, and returns a new object with three properties, userOne
, userTwo
and userThree
, that correspond to the latest values emitted by each of the three Observables.
forkJoin(this.sources)
.pipe(
map(([userOne, userTwo, userThree]) => {
return {
userOne: userOne,
userTwo: userTwo,
userThree: userThree,
};
})
)
.subscribe(console.log);
The extensive syntax reported above can be simplified as follows:
- Removing the
return
keyword when the returned object starts on the same line as the function. - Don’t repeat the assignment in the object if the value has the same name as the key.
forkJoin(this.sources)
.pipe(
map(([userOne, userTwo, userThree]) => ({
userOne,
userTwo,
userThree,
}))
)
.subscribe(console.log);
Finally, we can manipulate data in the map
operator by using object restructuring and updating the values.
map(([userOne, userTwo, userThree]) => ({
userOne: { ...userOne, name: 'Lorenzo' },
userTwo,
userThree,
}))
Basic Error Handling With ForkJoin
There are several error-handling strategies that depend on the context.
However, they all capitalize on the catchError
operator that catches errors in the source Observable and handles them appropriately by returning a new Observable or throwing an error.
The following example expands the code so that, should an error occur, the catchError
operator would catch it and return a new Observable.
forkJoin(this.sources)
.pipe(
map(([userOne, userTwo, userThree]) => ({
userOne: { ...userOne, name: 'Lorenzo' },
userTwo,
userThree,
})),
catchError((error) => of({ error }))
)
The of
operator is only a placeholder. You can use the emitted error value to inform the user, send the error to a server or a third party, or take some other action, such as logging the error.
Deprecated Syntax
forkJoin(
this.http.get<User>('https://jsonplaceholder.typicode.com/users/1'),
this.http.get<User>('https://jsonplaceholder.typicode.com/users/2'),
this.http.get<User>('https://jsonplaceholder.typicode.com/users/3')
)
This syntax is deprecated. As reported in the documentation, passing Observables directly as parameters is deprecated. This is called rest-parameter signature and it will be removed in RxJS v8.
RxJS suggests passing an array of sources, as we did in the examples above.
// From RxJS
// deprecated
forkJoin(odd$, even$);
// suggested change
forkJoin([odd$, even$]);
// or
forkJoin({odd: odd$, even: even$})
What Is an Operator Decision Tree?
You should familiarize yourself with the RxJS Operator Decision Tree. It will help you find or select the best operator for your needs.
And if you find RxJS annoying, Angular signals might be your way out, at least in the beginning