Introduction to error handling in Angular 7: Part 1 - Angular errorHandler
You will need to have Node 11+, Node Package Manager version 6+, Angular CLI 7+ and Angular 7+ installed on your machine.
In this tutorial, you will be introduced to errors in JavaScript, the try-catch concept and how it is used and a little introduction to handling internal errors with the Angular errorHandler.
Errors
One of my favorite movie stars, Samuel L. Jackson, was the very first person I heard the phrase “to err is human” from, and although I cannot remember the title of the particular movie now you can agree with me that the same thing happens with errors when writing code. As an Angular developer, you already know errors can be caused by personal mistakes, like having an import statement with the file extension name in Angular 7 and it shows up in your browser if you fail to catch it.
import { DataService } from 'src/app/services/data.service.ts';
Errors can arise from lack of testing, server communications, or even ambiguity of the Angular project you are working on. We are human beings and so are prone to errors, that is one reason a good editor like VS Code will always draw squeaky lines when you start derailing.
Prerequisites
To be able to follow through in this article’s demonstration you should have:
- Node version 11.0 installed on your machine.
- Node Package Manager version 6.7 (usually ships with Node installation).
- Angular CLI version 7.0
- The latest version of Angular (version 7)
// run the command in a terminal
ng version
Confirm that you are using version 7, and update to 7 if you are not.
Other things that will be nice-to-haves are:
- A working knowledge of the Angular framework at a beginner level.
- Familiarity with Angular services will be a plus but not a requirement.
Types of errors
There are many types of errors but I will be grouping them in two main categories: insider and outsider errors.
- Insider errors: (also known as client side errors) These are the ones you can be held responsible for, the syntax errors, package manager errors, reference errors, type errors and all types of client side errors that can be fixed by the developer within the Angular application.
- Outsider errors: (also known as server side errors) These ones can span from server errors, which mostly come with three digit status codes like 500 to internet network errors, to even browser specific errors. These are basically errors that are out of the reach of the Angular application hence the name outsider.
This article will focus on the insider errors and then a subsequent article will focus on the outsider errors.
Baby steps: throw and catch!
When you run a function in JavaScript, that function joins a kind of function execution queue and as the application runs and gets to its turn it leaves the queue and gets executed. So, if an error occurs, JavaScript throws an exception, which will immediately remove all the operations in the queue until the exception is handled.
On a basic level, exceptions are handled with try/catch blocks, the whole application crashes if the compiler does not see this try/catch block.
The try/catch syntax looks like this:
try {
throw new Error('An error occurred');
}
catch (error) {
console.error('Here is the error message', error);
}
console.log('Execution continues');
You will see the usefulness in a little demonstration. Create a new Angular project with the CLI, call it ngapp
ng new ngapp
Accept the router settings and choose plain CSS as the style sheet. Go to the app.component.ts
file and copy in the code below:
// src/app/app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
normalError() {
const babies = foo;
console.log("This is normal error without a try catch");
}
}
Copy these into the app.component.html
<!-- src/app/app.component.html -->
<button (click)="normalError()">
Fire Normal Error
</button>
Then copy these basic styling into the app.component.css
// src/app/app.component.css
button {
background-color: #4CAF50; /* Green */
border: none;
color: white;
padding: 15px 32px;
margin: 5px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
}
Run the application in development like this:
ng serve
In some cases it will fail to compile, when this happens re-run it and it will compile.
This shows in the console and most of the time, breaks the entire application immediately as you can see the message we logged did not show. The try/catch block handles these exceptions gracefully and the application continues running. So, if you had another button with a try catch block this time, you can spot the difference. Copy this in the app.component.html file
:
<!-- src/app/app.component.html -->
<button (click)="normalError()">
Fire Normal Error
</button>
<br>
<button (click)="errorWithCatch()">
Fire Error With Try Catch
</button>
Copy the code below in the app.component.ts
file:
// src/app/app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
normalError() {
const babies = foo;
console.log("This is normal error without a try catch");
}
errorWithCatch() {
try {
const babies = foo;
} catch (error) {
console.log(' — Error is handled gracefully: ', error.name);
}
console.log(' — Execution continues without app breaking');
}
}
The code in bold shows the same reference error instance, but this time around it is wrapped in a try-catch block so it can be handled gracefully. When you run the app again, it should look like this:
Limitations of try/catch
As great as try/catch is, thinking about the application you already see how it is not sustainable. This is because you can not go about adding these try/catch blocks in every function in your application, that is not resource effective. The good news is, Angular has a kind of global try/catch that we can use in one centralized location to catch all exceptions.
Error handler
Angular has a global error handling class called errorHandler that provides a hook for centralized exception handling inside your application. It basically intercepts all the errors that happen in your application, and logs all of them to the console, and stops the app from crashing (this was why your first button did not crash the app).
The syntax looks like this:
class MyErrorHandler implements ErrorHandler {
handleError(error) {
// do something with the exception
}
}
@NgModule({
providers: [{provide: ErrorHandler, useClass: MyErrorHandler}]
})
class MyModule {}
This lets us modify the default behaviour of logging errors to the console to our own logic after we have added it to our providers list in our app module. Go to the the app.component.ts
file and copy the code below in it:
// src/app/app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
normalError() {
const babies = foo;
}
errorWithCatch() {
const babies = foo;
}
}
The try catch block has been removed, so you should have two squeaky lines (or problems) in your code editor signifying two reference errors. Now to test the Angular global error handler, navigate to the app.module.ts
file and copy the code below:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, ErrorHandler } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
export class MyErrorHandler implements ErrorHandler {
constructor() {}
handleError(error: Error) {
if (Error) {
console.log("hi");
}
else console.log("hello");
}
}
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [
{
provide: ErrorHandler,
useClass: MyErrorHandler,
},
],
bootstrap: [AppComponent]
})
export class AppModule { }
The changes made just like the syntax suggests we created a global error handler class that implements the Angular error handler and then registered it in the provider section.
Our logic simply checks for errors and logs a message for any errors found. If you save this and run the application you will see the texts logged in the console for every error and the application working just like it was a try-catch.
Error service
Now you can see all our error logic is inside the core app module, this is not the way Angular encourages us to write code. So in line with keeping everything modular, we use a service. you will call it error service and all the logic on how to handle your errors will be kept in it.
First of all, generate an Angular service with the CLI:
ng generate service error
or
ng g s error
Then you have to remove the error handler logic in your app.module.ts
file so that it will look like this:
// src/app/app.component.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, ErrorHandler } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ErrorService } from './error.service';
import { ErrorComponent } from './error/error.component';
@NgModule({
declarations: [
AppComponent,
ErrorComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [
{
provide: ErrorHandler,
useClass: ErrorService,
},
],
bootstrap: [AppComponent]
})
export class AppModule { }
After that use the CLI to generate an error component where the user can be redirected to if an error occurs.
ng g c error
Now you have the error component you do no not need to modify it, navigate to the error.service.ts
file and copy the code below in it:
// src/app/error.service.ts
import { Injectable, ErrorHandler, Injector } from '@angular/core';
import { Router } from '@angular/router';
import { HttpErrorResponse } from '@angular/common/http'
@Injectable({
providedIn: 'root'
})
export class ErrorService implements ErrorHandler{
constructor(private injector: Injector) { }
handleError(error: any) {
const router = this.injector.get(Router);
if (Error instanceof HttpErrorResponse) {
console.log(error.status);
}
else {
console.error("an error occurred here broo");
}
router.navigate(['error']);
}
}
Here the logic is modified a bit to first check the kind of error it is, if it is an insider error then it navigates the user to an error page as the error occurs. So make sure your app-routing.module.ts
file is updated with the route like this:
// src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ErrorComponent } from './error/error.component';
const routes: Routes = [
{ path: 'error', component: ErrorComponent}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Finally, your app.component.html
file should look like this:
<div>
<button (click)="normalError()">
Fire Normal Error
</button>
<br>
<button (click)="errorWithCatch()">
Fire Error With Try Catch
</button>
</div>
<router-outlet></router-outlet>
If you followed the post to this point, you can run the application in development again, your errors will be gracefully handled as expected.
Conclusion
You have been introduced to errors and the concept behind handling them in Angular with the errorHandler class with focus on client side JavaScript errors. The next tutorial will go deep into outsider errors, the HTTP interceptors and error tracking with a remote service building on all the concepts you have learnt in this post. The complete code for this tutorial is on GitHub and can be found here.
20 July 2019
by Lotanna Nwose