So I'd normally write my http requests like so
Service
getData(){
return this.http.get('url')
}
Component
getTheData() {
this.service.getData().subscribe(
(res) => {
//Do something
},
(err) => {
console.log('getData has thrown and error of', err)
})
But looking through Angular documentation they seem to format it like so in a Service
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
catchError(this.handleError('getHeroes', []))
);
}
What's the implicit upside of this as it seems quite verbose to me and I've personally never had the need to pipe my errors.
According to Angular team
"handleError() method reports the error and then returns an innocuous result so that the application keeps working"
Because each service method returns a different kind of Observable result, function in catchError like handleError() here takes a type parameter so it can return the safe value as the type that the app expects.
Just came across this and thought I'd update my findings to better answer my own question.
While the main point of abstracting away the error handling logic from the component is a totally valid point and one the primary ones, there are several other reasons why the catchError is used over just handling the error with the subscription error method.
The primary reason is that the
catchError
allows you to handle the returned observable from either thehttp.get
or the first operator that errors within a pipe method i.e.:So if either of those operators fails, for whatever reason, the
catchError
will catch the observable error returned from that, but the major benefit that I've come across usingcatchError
is that it can prevent the observable stream from closing in the event of an error.Using the
throwError
orcatchError(err throw 'error occcured')
will cause the error portion of the subscription method to be invoked thus closing the observable stream, however usingcatchError
like so:Example one:
Example two:
1 It's all about separation of concern in Angular
One major benefit of using
catchError
is to separate the whole data retrieval logic including all errors that can occur along the way from the presentation of the data.1.1 Let Components only care about the presentation of data
Components should only care about data (whether it's there or not). They shouldn't care about the specifics of how to retrieve data or all the things that could go wrong during data retrieval.
Let's say your data is a list of items. Your Component would call a
service.getItemList()
function and, as it only cares about data, would expect:null
orundefined
You could easily handle all these cases with
ngIf
in your Component template and display the data or something else depending on the case. Having a Service function return a clean Observable that only returns data (ornull
) and isn't expected to throw any errors keeps the code in your Components lean as you can easily use the AsyncPipe in a template to subscribe.1.2 Don't let Components care about data retrieval specifics like errors
Your data retrieval and error handling logic may change over time. Maybe you're upgrading to a new Api and suddenly have to handle different errors. Don't let your Components worry about that. Move this logic to a Service.
1.3 Put the data retrieval and error handling logic in a Service
Handling errors is part of your data retrieval logic and not part of your data presentation logic.
In your data retrieval Service you can handle the error in detail with the
catchError
operator. Maybe there are some things you want to do on all errors like:Moving some of this into a
this.handleError('getHeroes', [])
function keeps you from having duplicate code.1.4 Make future development easier
There may come a time when you need to call an existing Service function from a new Component. Having your error handling logic in a Service function makes this easy as you won't have to worry about error handling when calling the function from your new Component.
So it comes down to separating your data retrieval logic (in Services) from your data presentation logic (in Components) and the ease of extending your app in the future.
2 Keeping Observables alive
Another use case of
catchError
is to keep Observables alive when you're constructing more complex chained or combined Observables. UsingcatchError
on inner Observables allows you to recover from errors and keep the outer Observable running. This isn't possible when you're using the subscribe error handler.2.1 Chaining multiple Observables
Take a look at this
longLivedObservable$
:The
longLivedObservable$
will execute a http request whenever a button is clicked. It will never terminate not even when the inner http request throws an error as in this casecatchError
returns an Observable that doesn't error but emits an empty array instead.If you would add an error callback to
longLivedObservable$.subscribe()
and removedcatchError
ingetHeroes
thelongLivedObservable$
would instead terminate after the first http request that throws an error and never react to button clicks again afterwards.Excursus: It matters to which Observable you add
catchError
Note that
longLivedObservable$
will terminate if you movecatchError
from the inner Observable ingetHeroes
to the outer Observable.Observables terminate when an Error (or Complete) notification is delivered. They can't emit anything else afterwards. Using
catchError
on an Observable doesn't change this.catchError
doesn't allow your source Observable to keep emitting after an error occurred, it just allows you to switch to a different Observable when an error occurs. This switch only happens once as only one error notification can be delivered.In the example above, when
this.getHeroes()
errors this error notification is propagated to the outer stream leading to an unsubscribe fromfromEvent(button, 'click')
andcatchError
switching toof([])
.Placing
catchError
on the inner Observable doesn't expose the error notification to the outer stream. So if you want to keep the outer Observable alive you have to handle errors withcatchError
on the inner Observable, i.e. directly where they occur.2.2 Combining multiple Observables
When you're combining Observables e.g. using
forkJoin
orcombineLatest
you might want the outer Observable to continue if any inner Observable errors.animals$
will emit an array containing the animal arrays it could fetch ornull
where fetching animals failed. e.g.Here
catchError
allows theanimals$
Observable to complete and emit something.If you would remove
catchError
from all fetch functions and instead added an error callback toanimals$.subscribe()
thenanimals$
would error if any of the inner Observables errors and thus not emit anything even if some inner Observables completed successfully.To learn more read: RxJs Error Handling: Complete Practical Guide