As is often the case, this piece grew out of my activity on StackOverflow.
The question:
Can we combine a list of observables to get a single cumulative result the way forkJoin does, while also receiving updates on how far along the process is?
There is a clear opportunity to build a custom RxJS utility here. Let's do it.
Understanding forkJoin
The official docs describe it like this:
__****forkJoin(…sources: any\[\]): Observable<any>****__
Accepts an Array of [ObservableInput](https://rxjs-dev.firebaseapp.com/api/index/type-alias/ObservableInput) or a dictionary Object of [ObservableInput](https://rxjs-dev.firebaseapp.com/api/index/type-alias/ObservableInput) and returns an [Observable](https://rxjs-dev.firebaseapp.com/api/index/class/Observable) that emits either an array of values in the exact same order as the passed array, or a dictionary of values in the same shape as the passed dictionary.
So in essence, forkJoin for observables behaves equivalently to how Promise.all functions for promises.
Here's the classical marble diagram:

A typical application is firing off many independent network calls — for instance, pulling the details for each user separately when we only have their IDs.
Consider this sample:

See the snippet for a cleaner look.
There is also a codepen available to play with.
This approach works well for aggregated results, but it offers no visibility into intermediate states — say, how many requests have already finished. The stock forkJoin cannot tell us this. That's a gap we can fill with a custom operator.
Here’s the plan for forkJoinWithProgress:
Output contract:
- It returns a higher-order Observable that emits a tuple of two Observables:
[finalResult$, percent$].
*Higher-order (HO) Observable - observable that emits other observables, so data flow should be handled with special flattening operators like mergeMap, switchMap, concatMap, etc... you can read more about it here and here.
percent$emits a numeric percentage of completed sources.finalResult$emits the accumulated results at the end (or an error if any source errors).
The intended usage is shown below:

Design details:
- The
forkJoinWithProgressfunction accepts an array of Observables and produces a higher-order Observable. - The returned Observable emits a tuple
[finalResult$, percent$](wrapped inof). - We inject side-effects into each of the input Observables: when one completes, we bump a counter, calculate completion percentage, and push it through the
percent$Subject (using thefinalizeoperator). - The
finalResult$is simply the classicforkJoinaggregate. - Each subscriber gets an independent execution — this is achieved by wrapping the whole flow in the RxJS
deferfactory (more about this pattern here). - Errors from any of the input Observables propagate directly to the
finalResult$subscriber.
A comprehensive RxJS course is available courtesy of Packtpub. It covers both fundamental and advanced topics and offers practical solutions for everyday dev tasks. Check it out!
Building the Operator
#1–2: Accept an array of Observables; return a higher-order Observable of [finalResult$, percent$].
We start with the skeleton of the function:

Now we have our arrayOfObservables (our AJAX Observables). The next step is to loop through them and attach the necessary logic.
#3: Add the side-effects and compute the percentage.
Here is how we modify the inputs:

We are augmenting each observable in the array. By mapping over the input, we attach a finalize operator to each.
When an observable finishes, the finalize callback runs: we increment the counter, divide by the total, multiply by 100, and send the result via the percent$ Subject.
#4: Bring in forkJoin for the final aggregation.

Let's walk through this chunk:
- We pass the modified list into
forkJoin, which subscribes to every observable and waits for all of them to emit and complete. - When the aggregate result is ready, the
tapcallback emits the final value of100topercent$and then completes the Subject. This is also a necessary memory-safety step. - Finally,
finalResult$itself emits the combined result to whoever subscribes.
Wrapping the Whole Thing in defer
The defer creation function guarantees that a brand new observable is produced for every subscriber. Without it, our counter and our percent$ Subject would be shared across subscriptions, polluting state. With it, each subscriber gets a fresh run with a fresh counter.

Here is the flow, step by step:
- The array of observables is accepted as input.
- Everything downstream of
deferruns once for each subscriber (indicated by the callback at the top). - We create the fresh
countervariable and thepercent$Subject inside that factory, guaranteeing isolation. - We map the original sources into a new array, decorating each with a
finalizehook that computes the percentage and pushes it through the Subject as each source completes. - The decorated array is passed to the standard RxJS
forkJoin. Thetapafter it ensures that onceforkJoinresolves, we emit a final100topercent$and close it. - The function returns a higher-order Observable that emits
[finalResult$, percent$].
Summing Up
You can see the complete working example in this codepen.

forkJoinWithPercent
Leveraging It Inside an Angular Application
The utility has been distributed as an npm package — rxjs-toolbox — so integrating it into your own projects is straightforward.
For a practical demonstration, check out this Stackblitz Angular demo. It employs the package to drive a loading bar's percentage indicator:

Within the Angular framework, the integration is seamless.
Further References
If you wish to dive deeper into various RxJS operator use cases, the following resources are recommended:
- “Retry vs Repeat”
- “RxJS: Managing Operator State” by Nicholas Jamieson.
- “RxJS ‘repeat’ operator — beginner necromancer guide”
- “Throttling notifications from multiple users with RxJS”
- rxjs-toolkit — RxJS Everyday Custom Operators by Jason Awbrey.
- backoff-rxjs — A collection of helpful RxJS operators to deal with backoff strategies by Alex Okrushko.
Enjoyed the read? Feel free to connect on Twitter!
Starting from section 4 of my RxJS video course advances staff is reviewed — so if you familiar with RxJS already — you can find something useful for you as well: higher-order observables, anti-patterns, schedulers, unit testing, etc! Give it a try!
Special thanks to Lars Gyrup Brink Nielsen, Nicholas Jamieson, Tim Deschryver and Michael Karén for reviewing this post and making many valuable remarks to make it better!
