15

Currently, my Angular application consists of a significant number of submodules and components. Angular applications are quite sensitive to html rendering errors. For example, if we get a NULL object from an API and trying to get access to its property, it partially breaks rendering of the application.

It is quite difficult to handle and test such cases taking into account that the application is constantly growing. Do you know if it is possible to create a script that can log all error appearing in a console?

Here what I have got:

1) An http requests error handle to log broken requests from an API:

  private handleError<T> (operation = 'operation', result?: T) {
    return (error: any): Observable<T> => {

      // TODO: send the error to remote logging infrastructure
      console.error(error); // log to console instead

      // TODO: better job of transforming error for user consumption
      this.log(`${operation} failed: ${error.message}`);

      // Let the app keep running by returning an empty result.
      return of(result as T);
    };
  }

2) Client-side logger but for whatever reason it only logs errors from crawlers :)

<script>
  window.onerror = function(m,u,l,c) {
    if (window.XMLHttpRequest) {
      var xhr = new XMLHttpRequest();
      var data = "msg="+encodeURIComponent(m)
        +"&url="+encodeURIComponent(u)
        +"&line="+l
        +"&col="+c
        +"&href="+encodeURIComponent(window.location.href);
      xhr.open("GET", "logger.php?"+data, true);
      xhr.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
      xhr.send();
    }
  };
</script>

It would be very useful to get a script that can simply log all errors from console and

3 Answers 3

45
+50

If you wish to catch and log all the errors (not only HTTP nature ones) in your Angular application I'd propose you the way that Sentry and other bug trackers use.

Angular has an ErrorHandler class which provides a hook for centralized exception handling.

So your task would be to create your own error handler. I usually do it like this:

import {ErrorHandler, Injectable} from '@angular/core';
import {LoggerService} from './some/logger.service';

@Injectable()
export class CustomErrorHandlerService extends ErrorHandler {

    constructor(private logger: LoggerService) {
        super();
    }

    handleError(error) {
        // Here you can provide whatever logging you want
        this.logger.logError(error);
        super.handleError(error);
    }
}

The next step is to use your error handler by means of Dependency Injection. Just add it in your AppModule like this:

import {CustomErrorHandlerService} from '~/app/_services/custom-error-handler.service';

@NgModule({
    imports: [...],
    declarations: [...],
    providers: [
        ...
        {provide: ErrorHandler, useClass: CustomErrorHandlerService},
    ],
    bootstrap: [AppComponent]
})

That's it. The key feature of such implementation is that this service handles all possible errors, appearing in your application (just like you ask in your question) including HTTP errors and others.

Please, leave your comments if you have any additional questions.

Sign up to request clarification or add additional context in comments.

2 Comments

what is LoggerService? how can i reach it ?
LoggerService is some service that you've created for logging. Here it's just an example of error handling. You can handle it in your own way without using some LoggerService i.e. handleError(error) {console.error(error); super.handleError(error);}
5

When I started working with Angular, I found this article helpful: Angular: Adding Logging in Angular Applications.

If you follow the guidance, you end up with a LoggingService that you can configure to log errors to one of three (or all) locations:

  • The console
  • Local Storage
  • A Database (via POST request)

What the article leaves out is how to catch the errors automatically. You can do this with an HttpInterceptor. Here's an example from an application I'm working on:

http-request-interceptor.ts

import { Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpRequest, HttpErrorResponse, HttpInterceptor } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { retry, catchError } from 'rxjs/operators';
import { Router } from '@angular/router';

import { LogService } from '../services/log.service';

@Injectable()
export class HttpRequestInterceptor implements HttpInterceptor {
  constructor (private logService: LogService, private router: Router) { }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const newReq = req.clone({
      headers: req.headers.set('Content-Type', 'application/json'),
      withCredentials: true
    });
    return next.handle(newReq)
      .pipe(
        retry(1),
        catchError((error: HttpErrorResponse) => {
          let displayError = '';
          let serverError = '';
          let clientError = '';

          if (error.error instanceof ErrorEvent) {
            // client-side error
            clientError = `Error: ${error.error.message}`;
            this.logService.error(clientError);
          } else {
            // server-side error
            displayError = error.error;
            serverError = `Error Code: ${error.status}\n${error.message}\n${error.error}`;

            if (error.status === 401) {
              this.logService.error(serverError);
              this.router.navigate(['/unauthorized', { message: error.error}]);
            }

            if (error.status >= 500) {
              this.logService.error(serverError);
              this.router.navigate(['/error']);
            }
          }
          return throwError(displayError);
        })
     );
  }
}

HttpInterceptors are very powerful. This one does the following:

  1. Adds a Content-Type to the header of every request, setting it to application/json
  2. Sends each request with credentials
  3. Retries each request a second time before error handling
  4. Handles errors
    • When the User is Unauthorized, it redirects to a custom 401 error page
    • When the Server throws an Error (500+), it redirects to a custom server error page
    • In all cases, it logs the error using the logging service mentioned above
    • It also throws the error back to the caller, so if your API returns user-friendly error messages you can catch them in your controller and show them to your users

To register an HttpInterceptor in your application you need a module:

http-interceptor.module.ts

import { NgModule } from '@angular/core';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { HttpRequestInterceptor } from './http-request-interceptor';


@NgModule({
  providers: [
    { provide: HTTP_INTERCEPTORS,
      useClass: HttpRequestInterceptor,
      multi: true
    }
  ]
})

export class HttpInterceptorModule { }

Then you register this module in your app.module.ts file and include it in the imports array.

2 Comments

Do you know how can I retry a failed request? For example, only if get 500 status code ?
Instead of calling .retry(1) which retries the request after any error, you can call .retryWhen(). Check out the Stack Overflow search results on retryWhen here
0

Although this is not an exact answer to your question, it might still be helpful to you.

In your Angular templates, you can use the Save Navigation Operator which checks whether a variable has a renderable value (anything but null or undefined). Otherwise, the expression will simply not be rendered and you'll receive no error at all. You just need to append a question mark after the variable you would like to render: <p>{{variable?}}</p>

If you still need to log all possible errors, you'll probably need to implement error handling to all your API service methods. The Official Angular Guide provides a very detailed explanation on HttpClient error handling.

3 Comments

You're welcome. If you think your question is solved, you could "accept" the answer.
Could you please specify what the remaining problem is exactly? Do you still need a service that should "globally" log errors?
yeah, you right - I think it should be a wrapper to the application or something like that ...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.