When should one include the error handler on an Observable? - javascript

I'm confused about general good practice when it comes to error handling. For example, if I'm already catching the error in my service, do I still need to include the error handler in my subscription?
Here's my http method in my service. As you can see it calls catchError:
deleteTask(id: number): Observable<any>{
return this.http.delete(this.tasksUrl+'/'+`${id}`)
.pipe(
catchError(this.handleError)
);
}
private handleError(res: HttpErrorResponse | any) {
console.error(res.error || res.body.error);
return observableThrowError(res.error || 'Server error');
}
And in my component:
delete(id: number){
this.deleteService.deleteTask(id).subscribe(
(val) => {
/*post processing functionality not relevant to this question
*/
}
);
}
In the angular documentation https://angular.io/guide/observables the error handler is described as optional:
myObservable.subscribe(
x => console.log('Observer got a next value: ' + x),
err => console.error('Observer got an error: ' + err),
() => console.log('Observer got a complete notification')
);
So in my example, would including the error handler on my subscription add anything to my code? Like if I did:
delete(id: number){
this.deleteService.deleteTask(id).subscribe(
(val) => {
/*post processing functionality not relevant to this question
*/
},
err => console.error('Observer got an error: ' + err)
);
Would it catch anything my catchError didn't catch? It almost feels like it would be good practice to always include the error handler, so I don't know why it's marked as optional? When should one use the subscription error handler vs other forms of error handling?

It's all about how you want to handle error in your application,
if you want to throw a fancy error instead of the actual error comming back from server, you can have a error handler in service end and who ever consume your service will get the fancy error instead of actual error.
// copied from your question
deleteTask(id: number): Observable<any>{
return this.http.delete(this.tasksUrl+'/'+`${id}`)
.pipe(
catchError(this.handleError)
);
}
private handleError(res: HttpErrorResponse | any) {
console.error(res.error || res.body.error);
return observableThrowError(res.error || 'Server error');
}
if not, you dont need to handle error in your service code, let the consumer(service/component) handle it.
deleteTask(id: number): Observable<any>{
return this.http.delete(this.tasksUrl+'/'+`${id}`);
}
// component
...
this.service.deleteTask(id).subscribe(success,(err) => {
// example
alert(err.message);
});
...
To handle common http errors (500, 401, 403, 404) you can write a HttpInterceptor, so that you dont need to write the error handling logic everywhere.
import { Injectable } from '#angular/core';
import {
HttpEvent, HttpRequest, HttpHandler, HttpInterceptor, HttpErrorResponse
} from '#angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
#Injectable()
export class MyAppHttpInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
// redirect to login page
} else {
return throwError(error);
}
})
);
}
}
if you want to log the errors to server or if you want to show a custom console or error notification over the screen on development mode or for debugging, you can create a global error handler service extending the existing ErrorHandler servcie in angular.
import { ErrorHandler } from '#angular/core';
#Injectable()
export class GlobalErrorHandler implements ErrorHandler {
handleError(error) {
// your custom error handling logic
}
}

Would it catch anything my catchError didn't catch?
No, you are just passing the same error through. But if your subscription doesn't have an error handler you will get an exception if you don't handle it. So you should either have an error handler on your subscription or pass an observable with no data.
const { throwError, of } = rxjs;
const { catchError } = rxjs.operators;
throwError('error').pipe(catchError(error => {
console.log('Caught error - ', error);
return of(null);
})).subscribe(val => { console.log('No error handler needed'); });
throwError('error').pipe(catchError(e => {
console.log('Caught error - ', e);
return throwError(e);
})).subscribe(val => {}, error => { console.log('Subscription handled error - ', error); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.3/rxjs.umd.min.js"></script>

Related

HttpClient & Rxjs

I am working on a case where during a network connection we sometimes might have a limited internet connectivity where we unable to get response from the server or failed response as HttpError.
I hereby trying to ping the URL every second to check whether we are getting response or not, for this
I am trying this code, this is working fine in online method but when i am turning my internet of is doesn't return me false value.
fetch-data.service.ts
import { Injectable } from '#angular/core';
import { HttpClient, HttpResponse, HttpErrorResponse } from '#angular/common/http';
import { Posts } from './posts';
import { Observable, interval, throwError, of } from 'rxjs';
import { take, exhaustMap, map, retryWhen, retry, catchError, tap, mapTo, } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class FetchDataService {
public url = 'https://jsonplaceholder.typicode.com/posts';
constructor(private _httpClient: HttpClient) { }
getData() {
const ob = interval(1000);
return ob.pipe(
exhaustMap(_ => {
return this._httpClient.get<Posts[]>(this.url, { observe: 'response' });
}),
map(val => {
if (val.status === 200)
return true;
throw val;
}),
retryWhen(errors => {
return errors.pipe(map(val => {
if (val.status === 0)
return false;
}))
})
);
}
// private handleError(error: HttpErrorResponse) {
// if (error.error instanceof ErrorEvent) {
// // A client-side or network error occurred. Handle it accordingly.
// console.error('An error occurred:', error.error.message);
// } else {
// // The backend returned an unsuccessful response code.
// // The response body may contain clues as to what went wrong,
// console.error(
// `Backend returned code ${error.status}, ` +
// `body was: ${error.error}`);
// if (error.status !== 200)
// return of(false);
// }
// // return an observable with a user-facing error message
// return throwError(
// 'Something bad happened; please try again later.');
// };
}
pulldata.component.html
import { Component, OnInit } from '#angular/core';
import { FetchDataService } from '../fetch-data.service';
import { Observable } from 'rxjs';
import { Posts } from '../posts';
#Component({
selector: 'app-pulldata',
templateUrl: './pulldata.component.html',
styleUrls: ['./pulldata.component.css']
})
export class PulldataComponent implements OnInit {
public data;
public error = '';
constructor(private _fecthDataServe: FetchDataService) { }
ngOnInit() {
this._fecthDataServe.getData().subscribe(val => {
this.data = val;
console.log(this.data);
});
}
}
what would be the best solution to check the internet connectivity in this manner?
My personal preference would be to not do this with HTTP because of data overhead. Every HTTP request will contain cookie data and other headers that are often useless in these kinds of scenarios.
Is it possible for you to use Web Sockets? With these, you can open up a connection to the server that, unlike HTTP, doesn't have to close. It can remain open forever. And you can subscribe to events to get notified about connection losses. Web Sockets also have the added benefit that it's a new protocol based on TCP, it's not HTTP, resulting in a lot less network data will have to be send.
let socket = new WebSocket('wss://yourserver/socket...');
socket.addEventListener('open', () => console.log('connection has been opened'));
socket.addEventListener('close', () => console.log('connection has been closed'));
In your situation, you might also want to check out the Reconnecting WebSocket, which reconnects when the connection drops. You could also write this small wrapper yourself, of course.
Also, what might even be a simpler solution. You can subscribe to online/offline events on the window object: read more on MDN
function updateOnlineStatus(event) {
var condition = navigator.onLine ? "online" : "offline";
// ...do something with the new status
}
window.addEventListener('online', updateOnlineStatus);
window.addEventListener('offline', updateOnlineStatus);
Both of these solutions should be easily wrappable in an Angular service, but let me know if that works out and/or if these solutions are an option for you.

Angular 6 Error Handling not showing toasts

I am using the code below to catch errors in my small Angular 6 app... I am catching the errors, I am showing info in the console the problem is that the toasts do not show unless I click somewhere in the application, they don't show up all by themselves. I am using the ToastrService in other parts of the application and whenever I call it like I do it here it shows toats without having to click on anything.
Any idea what might be causing this behaviour?
import { Injectable, ErrorHandler, Injector } from '#angular/core';
import { Router } from '#angular/router';
import { HttpErrorResponse } from '#angular/common/http';
import { ToastrService } from 'ngx-toastr';
import { AppSettings } from '../../app.settings';
#Injectable()
export class GlobalErrorHandler implements ErrorHandler {
constructor(private injector: Injector) {}
public handleError(error: any) {
const router = this.injector.get(Router);
const settings = this.injector.get(AppSettings)
const toastr = this.injector.get(ToastrService);
console.log(`Request URL: ${router.url}`);
console.log(error);
if (error instanceof HttpErrorResponse) {
console.log("it is");
if (error.status === 401) {
router.navigate(['/login']);
settings.settings.setLoadingSpinner(false);
toastr.error('Logged in user no longer authenticated on server.', 'Unable to connect to server');
} else if (error.status === 404) {
console.log("it is 404");
toastr.error('Unable to connect to server. Missing or wrong URL, please try again', 'Unable to connect to server');
settings.settings.setLoadingSpinner(false);
} else if (error.status === 0) {
toastr.error('Server appears to be temporary unavailable', 'Unable to connect to server');
settings.settings.setLoadingSpinner(false);
} else if (error.status === 500) {
toastr.error('Server appears to be temporary unavailable', 'Unable to connect to server');
settings.settings.setLoadingSpinner(false);
}
} else {
console.error(error);
toastr.error('An error has occured', 'Sorry');
}
}
}
I have added the provider in the module also to use this class as error handler.
please include the toaster service in constructor and run. As per my understanding, while service get instantiated, toaster should have to get initiated.
I believe you can import and include NgZone in your constructor to have this work as intended:
constructor(private injector: Injector, private zone: NgZone) {}
Then wrap your toastr calls:
this.zone.run(() => toastr.error('message', 'title'));

Angular global error handler prints error.message as the error itself

I have a global error handler in my application
#Injectable()
export class GlobalErrorHandlerService implements ErrorHandler {
constructor() {
console.log( 'error handler constructor');
}
handleError(error: any) {
console.log(error.message);
}
}
I throw an error in my service:
private sending(data) {
this.myApi.send({
type: 'json',
data: data
}).then(response => {
this.onChange.next(response);
}, httpError => { throw httpError; } );
}
The weird thing is, that in the error handler when I log error.message, the whole error gets printed not just the message part.
The error also has a "promise" and a "rejection" property, and the error status I want is at error.rejection.headers.status.
Thanks.

Where is the 'req' parameter defined?

I'm trying to implement an authentication scheme described here.
I'm struggling to find where the req parameter is defined in the code below. My code would not compile as it is not currently defined. This could be a typo in his code. I looked through the comments but nobody seems to have pointed that out:
// src/app/auth/jwt.interceptor.ts
// ...
import 'rxjs/add/operator/do';
export class JwtInterceptor implements HttpInterceptor {
constructor(public auth: AuthService) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).do((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// do stuff with response if you want
}
}, (err: any) => {
if (err instanceof HttpErrorResponse) {
if (err.status === 401) {
// redirect to the login route
// or show a modal
}
}
});
}
}"
Can someone point out what i'm missing?
Many thanks in advance.
Looks like a typo to me. The intercept function provides a parameter request - it should probably be referring to that instead of req.
The parameter must read as request as below
return next.handle(request)
.do(event => {
if ()

Angular2 httpClient - How can you get the content body?

Before I updated to HttpClient, I used the Http class and loaded data using the following code.
get(): Promise<Account> {
return this.http.get(this.apiUrl)
.toPromise()
.then((response) => response.json())
.catch(this.handleError);
}
private handleError(error: any) {
// do nothing for unauthorized user, as should be handles on component
if (error.status != '401') {
console.error('An error occurred', error);
}
return Promise.reject(error._body || error);
}
If the server errored with a 500, or 400 (validation) i could return the text from the server via my .catch handler.
Now I have updated to use the HttpClient and my code has changed to this
get(): Promise<Account> {
return this.httpClient.get<Account>(this.apiUrl)
.toPromise()
.catch(this.handleError);
}
private handleError(error: any) {
// do nothing for unauthorized user, as should be handles on component
if (error.status != '401') {
console.error('An error occurred', error);
}
return Promise.reject(error._body || error);
}
As you can see only the get method has changed, but now I cannot get the _body of the error response?
How can I get the body of the response like I did before?

Categories

Resources