Given the current workload, this will likely be one of the briefest pieces I’ve written. That might actually be a plus — it leaves no room for skipping it. The content is tight, and there’s a good chance you’ll pick up a useful trick or two.
The core issue
The focus here is on preventing HTTP calls from failing on unstable networks. Surprisingly, a 404 response can bring down your entire app when RxJS is involved.
Recall that RxJS observables emit three distinct event types:
next: pushes a new value into the streamerror: signals that something went wrongcomplete: indicates the stream has finished
One critical detail: an error event terminates the observable outright. Once an error is emitted, the stream is done and no further emissions occur.
You might think, “That’s manageable — I can just re-subscribe whenever I need fresh data.”
However, in a reactive architecture, this can become a real headache. Consider a typeahead search where every keystroke should return matches. You have a stream of search terms and use a switchMap to fire off an HTTP request for each term.
// this observable contains the values
// of what the user is searching for
// over time
const searchTerm$: Observable<string>;
// when the term receives a new value...
// go fetch some data
const results$ = searchTerm$.pipe(
switchMap(term => fetchData(term))
)
// subscribe to the observable to start listening
results$.subscribe((response: Result[]) => {
console.log(response);
})
This setup works smoothly until an error hits the stream. Whether it’s a poor connection, an unavailable server, or any HTTP failure (500, 404, and so on), the stream dies immediately. If the user’s network drops and a 404 slips through, the entire observable stops, and the app effectively breaks. No matter how many times the user types, no further requests will be made.
Handling errors with catchError
The catchError operator offers a way to intercept the error and substitute it with a fresh observable that carries the error as a value rather than re-throwing it. This lets you present a friendly notification to the user instead of letting the stream collapse. A basic implementation might look like this:
const results$ = searchTerm$.pipe(
switchMap(term =>
fetchData(term).pipe(
// return an observable with the error inside
catchError(e => of(e))
)
)
)
results$.subscribe(
(response: Result[] | HttpErrorResponse) => {
if(response instanceof HttpErrorResponse){
console.log('oh no:(');
return;
}
console.log('do something fancy');
});
)
Quick side note: I’m not advocating this as the ultimate error-handling pattern — just demonstrating the fundamentals.
Pay attention to where catchError is placed. It’s attached to the observable returned by fetchData(), not as a second operator in the initial pipe chain. The rationale: once an observable hits an error, it’s finished. Therefore, the correction must happen on the inner observable before the stream propagates the failure.
Retrying with retryWhen
So now the app survives errors, but what about a user on a train passing through a tunnel? The connection vanishes briefly, and the search yields nothing.
RxJS can handle this by instructing the stream to try again a set number of times:
const results$ = searchTerm$.pipe(
switchMap(term =>
fetchData(term).pipe(
retryWhen(e$ => e$.pipe(
// try again after 2 seconds
delay(2000),
// stop trying after 5 times
take(4)
)
// still keep the observable alive if
// the first 5 times fail
catchError(e => of(e))
)
)
)
For deeper insights into retryWhen, check the detailed documentation.
Leveraging the online event
While retrying is a solid approach, there’s an even more refined option: the HTML5 online event. This tells the browser to re-attempt the request the moment connectivity is restored. The code is more concise and quite elegant:
const results$ = searchTerm$.pipe(
switchMap(term =>
fetchData(term).pipe(
retryWhen(() => fromEvent(window, 'online'))
// still keep the observable alive if
// the server would return a different
// HTTP error
catchError(e => of(e))
)
)
)
Wrapping up
RxJS gives you remarkable command over HTTP requests! With a solid grasp of error handling, elevating your API calls becomes straightforward. This isn’t limited to typeahead scenarios — it applies to any observable where you merge an existing stream with an error-prone source like HTTP. The same issue can arise in NgRx effects or with the angular router.
I said it would be short, and I meant it. Hopefully, you gained some insight despite the brevity.
For further reading, don’t miss this piece: Power of RxJS when using exponential backoff.
Acknowledgments
- @AmarildoKurtaj — the final example was inspired by his suggestion.
Reviewers:

•