Let's face it: your Angular app will encounter HTTP errors from time to time. When that happens, you'd better be ready to handle them.

Fortunately, you can do handle them very easily with an HTTP Interceptor.

In fact, I've already shown you how to use an HTTP Interceptor to inject a JSON web token (JWT) into your request headers. In this guide, I'll show how to use another interceptor to handle error responses.

I'll also show you a great way to handle a specific type of error response you'll sometimes encounter.

If you're bored already, you can just go grab the code on GitHub. Otherwise, hang around for a bit.

Remember: if you're having problems running the Angular app locally, be sure to check the README.

Please note: this guide is a part of an ongoing series of tutorials on how to create a customer relationship management (CRM) application from scratch. The GitHub code for each guide belongs to a specific branch. The master branch holds the entire application up to the current date. If you want to follow the whole series, just view the careydevelopmentcrm tag. Note that the website displays the guides in reverse chronological order so if you want to start from the beginning, go to the last page.

The Business Requirements

Your boss Smithers drops by your office on the way to his annual performance review. He sits down, puts his feet on your desk, and says, "We've got a problem."

You sit back, wondering what the problem is.

"Although the profile photo upload thing works great right now, we need to handle unhappy path scenarios," Smithers says.

You sit up, surprised that Smithers knows about unhappy path scenarios.

"For example, before you put in that interceptor thing, if the user got a security error, the application didn't handle it properly. We need to handle error paths from downstream services!"

You sit up even more, surprised that Smithers knows about downstream services.

"Please take care of it right away!"

He leaves your office muttering something about how he doesn't like talking about his attitude during annual reviews.

Continued Adjustments

If you're following along with these guides, be advised that I've once again made several changes that have nothing to do with the subject of this article. Therefore, I won't be covering them here.

Keep in mind, though, that most of the changes are cosmetic.

So once again it's best just to grab the whole source tree and start from scratch.

A New Module!

Yes, it's that time of the year again. Time for a new module!

What module is that? The route message module!

[Wait for applause]

What does anything called a route message module have to do with handling HTTP errors you might ask? And I answer.

The new module is what you'll use to handle specific types of HTTP errors. In this case, it will handle an unauthorized (401) error.

When the user encounters that error, the application will assume that the user doesn't belong anywhere in the app, proceed to log the user out, and return the user to the login page.

However, it will do so with a message.

The user will see a message on the login page explaining what's going on.

And that's the point of the route message module.

Now, if you've been following along, you might wonder why you should create a new module instead of using the alert module that you just created.

In fact, you will use the alert module you just created. The route message module will rely on the alert module.

But the difference here is that alerts are designed display on the current page. Route messages, on the other hand, will display on a new page after the app has routed the user to a different location.

So without further ado, let's get going.

Go to your command line and navigate to the root of the source tree. Then enter these two commands at the command line:

ng g m ui/route-message

ng g c ui/route-message

Be patient as everything processes. Then, head back to the source in Microsoft Visual Studio. You should see several new files in src/app/ui/route-message

You'll need to edit some of those.

Don't Shoot the Messenger

But before you can edit anything, you need to create a new class: route-message.service.ts. Make it look like this:

import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class RouteMessageService {
  private _message: string = null;

  get message(): string {
    const returnedMessage = this._message;
    this.clear();

    return returnedMessage;
  }

  set message(val: string) {
    this._message = val;
  }

  clear() {
    this.message = null;
  }
}

In a nutshell, the service acts as a queue with 0 or 1 messages. Any time any other object "gets" the message, the queue is automagically cleared.

Now, edit route-message.component.ts. Make it look like this:

import { Component, OnInit } from '@angular/core';
import { RouteMessageService } from './route-message.service';
import { AlertService } from '../alert/alert.service';

@Component({
  selector: 'route-message',
  templateUrl: './route-message.component.html',
  styleUrls: ['./route-message.component.css']
})
export class RouteMessageComponent implements OnInit {

  currentMessage: string = null;

  constructor(private routeMessageService: RouteMessageService, private alertService: AlertService) { }

  ngOnInit(): void {
    this.currentMessage = this.routeMessageService.message;
    if (this.currentMessage) this.alertService.info(this.currentMessage);
  }
}

For starters, take note of the "route-message" selector. That means if you want to use this component in one of your HTML templates, you'll need to do so with the <route-message> element.

The class includes one property: a string called currentMessage.  You won't be shocked to learn that string holds the message that the component will dislpay.

However, it defaults to null, meaning there's currently no message to display.

On initialization, the component goes to service you just created and looks for a message. If there is one, it creates an info alert with that message.

That's it. That's the component class.

Wouldn't it be nice if they were all that easy?

Next, edit route-message.component.html.

<div *ngIf="currentMessage">
  <alert></alert>
</div>

Yeah that's pretty easy, too.

All it does is display an alert if, in fact, there's a message to display at all.

Now edit route-message.module.ts.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouteMessageComponent } from './route-message.component';
import { AlertModule } from '../alert/alert.module';

@NgModule({
  declarations: [RouteMessageComponent],
  imports: [
    CommonModule,
    AlertModule
  ],
  exports: [RouteMessageComponent]
})
export class RouteMessageModule { }

The big deal there is that the code exports RouteMessageComponent so other modules that import this module can use it.

Okay, now let's create another interceptor.

On the Hunt for Errors

In Microsoft Visual Studio, navigate to src/app/util. Create a new file called http-error-interceptor.ts.

Populate that file with this source:

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError, of } from 'rxjs';
import { catchError, retry } from 'rxjs/operators';
import { AuthenticationService } from '../services/authentication.service';
import { Router } from '@angular/router';
import { RouteMessageService } from '../ui/route-message/route-message.service';

@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {

  constructor(private authenticationService: AuthenticationService, private router: Router,
    private routeMessageService: RouteMessageService) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    let handled: boolean = false;

    return next.handle(request)
    .pipe(
      retry(1),
      catchError((returnedError) => {
        let errorMessage = null;

        if (returnedError.error instanceof ErrorEvent) {
          errorMessage = `Error: ${returnedError.error.message}`;
        } else if (returnedError instanceof HttpErrorResponse) {
          errorMessage = `Error Status ${returnedError.status}: ${returnedError.error.error} - ${returnedError.error.message}`;
          handled = this.handleServerSideError(returnedError);
        } 

        console.error(errorMessage ? errorMessage : returnedError);

        if (!handled) {
          if (errorMessage) {
            return throwError(errorMessage);
          } else {
            return throwError("Unexpected problem occurred");
          }
        } else {
          return of(returnedError);
        }
      })
    )
  }

  private handleServerSideError(error: HttpErrorResponse): boolean {
    let handled: boolean = false;

    switch (error.status) {
      case 401:
        this.routeMessageService.message = "Please login again.";
        this.authenticationService.logout();
        handled = true;
        break;
      case 403:
        this.routeMessageService.message = "Please login again.";
        this.authenticationService.logout();
        handled = true;
        break;
    }

    return handled;
  }
}

Well that one's a little more complicated than some of the other classes, isn't it?

I won't rehash the story of interceptors again. You can read more about them in the previous guide on the subject.

What I will point out, though, is that this new interceptor will work together with the other JWT interceptor.

Remember, Angular allows you to chain interceptors. So you can create as many as you want.

The first one handles JWT injection. This one will handle errors.

So what is this one doing? First of all, notice that it implements the intercept() method. That's a requirement for all interceptors.

Within that method it's invoking pipe() on the Observable created by next.handle(request).

The pipe() method makes code more readable when multiple operators are invoked on an Observable. Here, the code is using both catchError and retry.

Speaking of retry, that's telling Angular to give the request another shot in case an error is returned the first time. The number in parentheses (1) tells Angular to retry the request only one time.

The catchError operator lives up to its name. That's where the code will handle errors returned by the downstream service.

If the error is an instance of ErrorEvent, it's a client side issue. The code will just log that for now.

On the other hand, if it's an instance of HttpErrorResponse, then it will handle the error with some additional logic.

First it will create a message using template literals. That message includes the status code, the message, and the message detail.

For the purposes of this guide, the status code will be a 401, the message will be "Unauthorized," and the message detail will be "Full authentication is required to access this resource."

However, other HTTP errors will return different codes, messages, and details.

The code then calls the handleServerSideError() method to respond based on the nature of the error. If it's a 401 or 403 error, it logs the user out and reroutes the user to the login page with a message ("Please login again.").

Let's stop here for a moment and consider the use case: suppose the user walked away from the PC for a while and came back later to use the app. However, while the user was away the JWT expired.

In that case, the next time the user tries to do anything (like upload a profile photo), the downstream service will return a 401. So the app will reroute the user to the login page with a message saying that a new login is required.

That's exactly the situation this solution will handle.

Speaking of handling stuff, pay attention to the boolean that gets thrown around in that code: handled

That boolean defaults to false. It gets set to true if the code handled the error within the interceptor. If it doesn't get handled here, it gets thrown back to the object that made the request so it can handle the problem accordingly.

Updated Gateway

I've mentioned repeatedly that the app needs to display a message to the user after rerouting to the login page. Now it's time to implement the code that does that.

First, update login.module.ts:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LoginComponent } from './login.component';
import { RouterModule } from '@angular/router';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button';
import { ReactiveFormsModule } from '@angular/forms';
import { RouteMessageModule } from '../ui/route-message/route-message.module';

export const routes = [
    { path: '', component: LoginComponent }
];

@NgModule({
  declarations: [LoginComponent],
  imports: [
    CommonModule,
    FlexLayoutModule,
    MatIconModule,
    MatInputModule,
    MatButtonModule,
    ReactiveFormsModule,
    RouteMessageModule,
    RouterModule.forChild(routes)
  ]
})
export class LoginModule { }

The big change there is it's importing RouteMessageModule.

Now update the code that displays the right half of the screen in login.component.html

<div fxFlex="50" fxFlex.lt-md="100" fxLayout="column" class="mat-app-background">
      <div fxLayout="column" fxLayoutAlign="center" style="margin-top:30px">
        <div fxLayout="row" fxLayoutAlign="center">
          <route-message style="width:100%"></route-message>
        </div>

        <div fxLayout="row" fxLayoutAlign="center">
          <h3>Welcome to the CRM App</h3>
        </div>

        <div fxLayout="row" fxLayoutAlign="center">
          <h4>Please sign in below</h4>
        </div>
      </div>

      <div fxLayout="column" fxLayoutAlign="center" style="margin-top:100px">
        <form [formGroup]="form" (ngSubmit)="onSubmit(form.value)">
          <div fxLayoutAlign="center">
            <mat-form-field style="width:50%" appearance="fill">
              <mat-label>Enter username</mat-label>
              <input formControlName="username" matInput>
              <mat-error *ngIf="form.controls['username'].invalid">Please enter a valid username</mat-error>
            </mat-form-field>
          </div>
          <div fxLayoutAlign="center" style="margin-top:10px">
            <mat-form-field style="width:50%" appearance="fill">
              <mat-label>Enter password</mat-label>
              <input type="password" formControlName="password" matInput>
              <mat-error *ngIf="form.controls['password'].invalid">Please enter a valid password</mat-error>
            </mat-form-field>
          </div>
          <div fxLayoutAlign="center" style="margin-top:20px">
            <button [disabled]="!form.valid || formSubmitted" type="submit" mat-raised-button color="primary">Login</button>
          </div>
        </form>
        </div>
    </div>

The key difference there is the <route-message> element towards the top. That's where the message will appear.

Give and Take

Now it's time to revisit an old friend: app.module.ts. Update the providers array to look like this:

  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: HttpErrorInterceptor, multi: true }
  ]

If you've been following along with these guides, you'll see what's going on there.

The new code only uses the interceptor you just created. JwtInterceptor has been removed.

Why? It's a temporary thing only for testing purposes.

You want to see what happens when a user tries to upload a profile photo without sending the required token. That should give you a 401 response and the application should forward you back to the login page with a pretty message displayed at the top.

But Will It Fly?

Now it's time to see if this thing actually works as advertised.

Start your Spring Boot user service. Then, launch the Angular app. 

Navigate to http://localhost:4200/login and login with the usual credentials (darth/thedarkside).

Now go to User > Profile Image. You should see a familiar page that now takes up the whole screen because I decided that doing it the way the cool kids do it didn't work for this application.

 

Go through the familiar process of uploading an image and click Save Image.

And after a few seconds, you should see this:

 

Awesome! It worked!

You can check the console log to see that you got back a 401 error. 

In fact, if you check the console log, you'll see that you got two (2) 401 errors. How did you get two of them? 

Remember: the app will retry once on failed requests. That's why you got two errors.

Anyhoo, the logic here is that once a user gets a 401 error, the app assumes the user has no business doing anything more in the app and boots him or her out right away. 

But it also offers a nice message explaining what happened.

Now the user can login again and the JWT should last 24 hours.

All is well.

Wrapping It Up

Before you tell yourself that you're done here, make sure you add back the JwtInterceptor:

  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
    { provide: HTTP_INTERCEPTORS, useClass: HttpErrorInterceptor, multi: true }
  ]

Now you'll send the JWT with requests every time.

Try to upload a profile photo once that code is in place. It should work like a champ.

Oh, by the way: you might want to go to your operating system and delete all the extra profile photos you've created if you've been following these guides.

And when you're done with that, you can grab all the code on GitHub.

Have fun!

Image by Steve Buissinne from Pixabay