Angular CatchError is an RxJs Operator. We can use it to handle the errors thrown by the Angular Observable. Like all other RxJs operators, the CatchError also takes an observable as input and returns an observable (or throws an error). We can use CatchError to provide a replacement observable or throw a user-defined error. Let us learn all these in this tutorial.
Catch operator was renamed as catchError in RxJs 5.5, Hence if you are using Angular 5 or prior version then use catch instead of catchError.
We can handle the errors at two places.
We subscribe to an Observable by using the subscribe method. The subscribe method accepts three callback methods as arguments. They are the next value, error, or complete event. We use the error callback to catch & handle the errors.
For Example, consider the following code. The obs observable multiplies the values (srcArray) by 2 using the map operator. If the result is NaN, then we throw an error using throw new Error("Result is NaN").
srcArray = from([1, 2, 'A', 4]);
obs = this.srcArray
.pipe(
map(val => {
let result = val as number * 2;
if (Number.isNaN(result)) {
console.log('Errors Occurred in Stream')
throw new Error("Result is NaN")
}
return result
}),
);
ngOnInit() {
this.obs.subscribe(
el => {
console.log('Value Received ' + el)
},
err => {
console.log("Error caught at Subscriber " + err)
},
() => console.log("Processing Complete.")
)
}
**** Output *****
Value Received 2
Value Received 4
Errors Occurred in Stream
Error Caught at subscriber Error: Result is NaN
We subscribe and start to receive the values from the obs observable in the ngOnInit method. When the observable stream throws an error, it invokes the error callback. In the error callback, we decide what to do with the error.
Note that once the observable errors out it will not emit any values neither it calls the complete callback. Our subscription method will never receive the final value of 8.
Another option to catch errors is to use the CatchError Operator. The CatchError Operators catches the error in the observable stream as and when the error happens. This allows us to retry the failed observable or use a replacement observable.
To use CatchError operator, we need to import it from the rxjs/operators as shown below
import { catchError } from 'rxjs/operators'
The catchError is a pipeable operator. We can use it in a Pipe method similar to the other operators like Map, etc.
The catchError operator gets two argument.
The first argument is err, which is the error object that was caught.
The second argument is caught, which is the source observable. We can return it back effectively retrying the observable.
The catchError must return a new observable or it can throw an error.
The following examples shows the use of catchError operator.
srcArray = from([1, 2, 'A', 4]);
obs = this.srcArray
.pipe(
map(val => {
let result = val as number * 2;
if (Number.isNaN(result)) {
console.log('Errors Occurred in Stream')
throw new Error("Result is NaN")
}
return result
}),
catchError(error => {
console.log('Caught in CatchError. Returning 0')
return of(0); //return from(['A','B','C'])
})
);
//Output
Value Received 2
Value Received 4
Errors Occurred in Stream
Caught in CatchError. Returning 0
Value Received 0
Observable Completed
In the code above, the map emits the values 2 & 4, which is input to the catchError. Since there are no errors, catchError forwards it to the output. Hence the subscribers receive values 2 & 4.
The catchError comes into play, when the map operator throws an error. The catchError handle the error and must return a new observable (or throw an error). In the example above we return a new observable i.e. of(0). You can also emit any observable for example return from(['A','B','C']) etc
You can also return the original observable. Just use the return this.obs;
instead of return of(0);. But beware, It will result in an infinite
loop.
The new observable is automatically subscribed and the subscriber gets the value 0. The new observable now finishes and emits the complete event.
Since the original observable ended in a error, it will never emit the the value 8.
catchError can also throw an error. In the following example, we use the throw new Error(error) to throw a JavaScript error. This error will propagate to the subscriber as shown in the example below.
obs = this.srcArray
.pipe(
map(val => {
let result = val as number * 2;
if (Number.isNaN(result)) {
console.log('Errors Occurred in Stream')
throw new Error("Result is NaN")
}
return result
}),
catchError(error => {
console.log('Caught in CatchError. Throwing error')
throw new Error(error)
})
);
//OUTPUT
Value Received 2
Value Received 4
Errors Occurred in Stream
Caught in CatchError. Throwing error
Error caught at Subscriber Error: Error: Result is NaN
We can also make use of throwError to return an observable. Remember that the throwError does not throw an error like throw new Error but returns an observable, which emits an error immediately.
obs = this.srcArray
.pipe(
map(val => {
let result = val as number * 2;
if (Number.isNaN(result)) {
console.log('Errors Occurred in Stream')
throw new Error("Result is NaN")
}
return result
}),
catchError(error => {
console.log('Caught in CatchError. Throwing error')
return throwError(error);
})
);
//OUTPUT
Value Received 2
Value Received 4
Errors Occurred in Stream
Caught in CatchError. Throwing error
Error caught at Subscriber Error: Result is NaN
You can also retry the observable using the Retry operator.
obs = this.srcArray
.pipe(
map(val => {
let result = val as number * 2;
if (Number.isNaN(result)) {
console.log('Errors Occurred in Stream')
throw new Error("Result is NaN")
}
return result
}),
retry(2),
catchError((error,src) => {
console.log('Caught in CatchError. Throwing error')
throw new Error(error)
})
);
//Output
Value Received 2
Value Received 4
Errors Occurred in Stream
Value Received 2
Value Received 4
Errors Occurred in Stream
Value Received 2
Value Received 4
Errors Occurred in Stream
Caught in CatchError. Throwing error
Error caught at Subscriber Error: Error: Result is NaN
The catchError gets the source observable as the second argument. If we return it, it will get subscribed again effectively retrying the observable.
Ensure that you keep track of no of tries so that you can stop the observable after a few failed attempts. Otherwise, you may run into an infinite loop if the observable always emits an error.
obs = this.srcArray
.pipe(
map(val => {
let result = val as number * 2;
if (Number.isNaN(result)) {
console.log('Errors Occurred in Stream')
throw new Error("Result is NaN")
}
return result
}),
catchError((error,src) => {
console.log('Caught in CatchError. Throwing error')
this.count++;
if (this.count < 2) {
return src;
} else {
throw new Error(error)
}
})
);