While sending requests to our web API server, we can get an error in response. Therefore, using Angular error handling to handle those errors while sending HTTP requests is a must. That’s exactly what we are going to do in this post. If we get the 404 or the 500 error, we are going to redirect the user to a specific page. For other errors, we are going to show an error message in a modal form. The page that handles the 404 error is already created, so, let’s continue on by creating a 500 (Internal server error) component.
For the complete navigation and all the basic instructions of the Angular series, check out: Introduction of the Angular series.
Let’s start.
Creating the Internal Server Error Component
In the error-pages folder, we are going to create a new component by typing the AngularCLI
command:
ng g component error-pages/internal-server --skip-tests
Then, let’s modify the internal-server.component.ts
:
import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-internal-server', templateUrl: './internal-server.component.html', styleUrls: ['./internal-server.component.css'] }) export class InternalServerComponent implements OnInit { errorMessage: string = "500 SERVER ERROR, CONTACT ADMINISTRATOR!!!!"; constructor() { } ngOnInit(): void { } }
Then, let’s modify the internal-server.component.html
file:
<p> {{errorMessage}} </p>
Additionally, we are going to modify the internal-server.component.css
file:
p{ font-weight: bold; font-size: 50px; text-align: center; color: #c72d2d; }
Finally, let’s modify the routes
array in the app-routing.module.ts
file:
const routes: Routes = [ { path: 'home', component: HomeComponent }, { path: 'owner', loadChildren: () => import('./owner/owner.module').then(m => m.OwnerModule) }, { path: '404', component: NotFoundComponent }, { path: '500', component: InternalServerComponent }, { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: '**', redirectTo: '/404', pathMatch: 'full' } ];
That’s it.
We have created our component and it is time to create a service for error handling.
Creating a Service for Angular Error Handling
In the shared/services folder, we are going to create a new service and name it error-handler:
ng g service shared/services/error-handler --skip-tests
Next, let’s modify that error-handler.service.ts
file:
import { HttpErrorResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; @Injectable({ providedIn: 'root' }) export class ErrorHandlerService { public errorMessage: string = ''; constructor(private router: Router) { } public handleError = (error: HttpErrorResponse) => { if (error.status === 500) { this.handle500Error(error); } else if (error.status === 404) { this.handle404Error(error) } else { this.handleOtherError(error); } } private handle500Error = (error: HttpErrorResponse) => { this.createErrorMessage(error); this.router.navigate(['/500']); } private handle404Error = (error: HttpErrorResponse) => { this.createErrorMessage(error); this.router.navigate(['/404']); } private handleOtherError = (error: HttpErrorResponse) => { this.createErrorMessage(error); //TODO: this will be fixed later; } private createErrorMessage = (error: HttpErrorResponse) => { this.errorMessage = error.error ? error.error : error.statusText; } }
First of all, we inject the Router
, which we use to redirect the user to other pages from the code. In the handleError()
function, we check for the error’s status code and based on that we call the right private method to handle that error. The handle404Error()
and handle500Error()
functions are responsible for populating the errorMessage
property. We are going to use this property as a modal error message or an error page message. We are going to deal with the handleOtherError()
function later on, thus the comment inside.
If you remember the owner-list.component.ts
file, we are fetching all the owners from the server. But there is no error handling in that file. So let’s continue by modifying that owner-list.component.ts
file to implement the Angular error handling functionality:
import { Component, OnInit } from '@angular/core'; import { Owner } from './../../_interfaces/owner.model'; import { OwnerRepositoryService } from './../../shared/services/owner-repository.service'; import { ErrorHandlerService } from './../../shared/services/error-handler.service'; import { HttpErrorResponse } from '@angular/common/http'; @Component({ selector: 'app-owner-list', templateUrl: './owner-list.component.html', styleUrls: ['./owner-list.component.css'] }) export class OwnerListComponent implements OnInit { owners: Owner[]; errorMessage: string = ''; constructor(private repository: OwnerRepositoryService, private errorHandler: ErrorHandlerService) { } ngOnInit(): void { this.getAllOwners(); } private getAllOwners = () => { const apiAddress: string = 'api/owner'; this.repository.getOwners(apiAddress) .subscribe({ next: (own: Owner[]) => this.owners = own, error: (err: HttpErrorResponse) => { this.errorHandler.handleError(err); this.errorMessage = this.errorHandler.errorMessage; } }) } }
That’s it. We have to pay attention that now, we are passing a JSON object inside the subscribe
function. The next
property will trigger if the response is successful, and the error
property will handle the error response.
We can try it out by changing the code in the server’s GetAllOwners
method. Just as a first code line, we can add return NotFound()
or return StatusCode(500, “Some message”)
, and we are going to be redirected to the right error page for sure.
Preparation for the Owner-Details Component
Let’s continue by creating the owner-details component:
ng g component owner/owner-details --skip-tests
To enable routing to this component, we need to modify the owner-routing.module.ts
file:
const routes: Routes = [ { path: 'list', component: OwnerListComponent }, { path: 'details/:id', component: OwnerDetailsComponent } ];
As you can see, the new path has the id parameter. So when we click on the Details button, we are going to pass this id
to our route and fetch the owner with that exact id in the OwnerDetails
component.
But, in order to be able to do that, we need to add a new interface to the _interfaces
folder:
export interface Account{ id: string; dateCreated: Date; accountType: string; ownerId?: string; }
And modify the Owner
interface:
import { Account } from './account.model'; export interface Owner{ id: string; name: string; dateOfBirth: Date; address: string; accounts?: Account[]; }
By using a question mark, we are making our property optional.
To continue, let’s change the owner-list.component.html
file:
<td><button type="button" id="details" class="btn btn-primary" (click)="getOwnerDetails(owner.id)">Details</button></td>
On a click event, we call the getOwnerDetails
function and pass the owner’s id as a parameter. So we need to handle that click event in our owner-list.component.ts
file.
First, let’s add an import statement:
import { Router } from '@angular/router';
Then, we are going to modify the constructor to add the router:
constructor(private repository: OwnerRepositoryService, private errorHandler: ErrorHandlerService, private router: Router) { }
And add the getOwnerDetails(id)
function:
public getOwnerDetails = (id) => { const detailsUrl: string = `/owner/details/${id}`; this.router.navigate([detailsUrl]); }
We create a URI for our details component with the id parameter and then call the navigate function to navigate to that component.
Finally, let’s just add one more function to fetch a single owner inside the owner-repository.service.ts
file:
public getOwner = (route: string) => { return this.http.get<Owner>(this.createCompleteRoute(route, this.envUrl.urlAddress)); }
Implementation of the Owner-Details Component
We have all the code to support the owner-details component. Now it is time to implement business logic inside that component.
Firstly, let’s modify the owner-details.component.ts
file:
import { HttpErrorResponse } from '@angular/common/http'; import { Component, OnInit } from '@angular/core'; import { Owner } from './../../_interfaces/owner.model'; import { Router, ActivatedRoute } from '@angular/router'; import { OwnerRepositoryService } from './../../shared/services/owner-repository.service'; import { ErrorHandlerService } from './../../shared/services/error-handler.service'; @Component({ selector: 'app-owner-details', templateUrl: './owner-details.component.html', styleUrls: ['./owner-details.component.css'] }) export class OwnerDetailsComponent implements OnInit { owner: Owner; errorMessage: string = ''; constructor(private repository: OwnerRepositoryService, private router: Router, private activeRoute: ActivatedRoute, private errorHandler: ErrorHandlerService) { } ngOnInit() { this.getOwnerDetails() } getOwnerDetails = () => { const id: string = this.activeRoute.snapshot.params['id']; const apiUrl: string = `api/owner/${id}/account`; this.repository.getOwner(apiUrl) .subscribe({ next: (own: Owner) => this.owner = own, error: (err: HttpErrorResponse) => { this.errorHandler.handleError(err); this.errorMessage = this.errorHandler.errorMessage; } }) } }
It is pretty much the same logic as in the owner-list.component.ts
file, except now we have the ActivatedRoute
imported because we have to get our id from the route.
After we execute the getOwnerDetails
function, we are going to store the owner object with all related accounts inside the owner
property.
All we have to do is to modify the owner-details.component.html
file:
<div class="card card-body bg-light mb-2 mt-2"> <div class="row"> <div class="col-md-3"> <strong>Owner name:</strong> </div> <div class="col-md-3"> {{owner?.name}} </div> </div> <div class="row"> <div class="col-md-3"> <strong>Date of birth:</strong> </div> <div class="col-md-3"> {{owner?.dateOfBirth | date: 'dd/MM/yyyy'}} </div> </div> <div class="row" *ngIf='owner?.accounts.length <= 2; else advancedUser'> <div class="col-md-3"> <strong>Type of user:</strong> </div> <div class="col-md-3"> <span class="text-success">Beginner user.</span> </div> </div> <ng-template #advancedUser> <div class="row"> <div class="col-md-3"> <strong>Type of user:</strong> </div> <div class="col-md-3"> <span class="text-info">Advanced user.</span> </div> </div> </ng-template> </div> <div class="row"> <div class="col-md-12"> <div class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th>Account type</th> <th>Date created</th> </tr> </thead> <tbody> <tr *ngFor="let account of owner?.accounts"> <td>{{account?.accountType}}</td> <td>{{account?.dateCreated | date: 'dd/MM/yyyy'}}</td> </tr> </tbody> </table> </div> </div> </div>
Here, we display the owner entity with all the required data. Also, if the owner has more than two accounts we conditionally show a different template (#advancedUser) for that field. Finally, we display all the accounts related to this owner:
We can also take a look at the advanced user:
Conclusion
By reading this post we’ve learned how to handle errors in a separate service by using Angular Error Handling. Also, we used conditional HTML rendering to show different data on the page with all the required details.
In the next part of the series, I am going to show you how to create child components and how to use @Input, @Output, and EventEmmiters in Angular. By doing so, we are going to learn to split our components into smaller parts (parent-child relation).