Angular HTTP interceptors, making development DRY

I’ve been building on an Angular app for a while now learning and strengthening my way through the ecosystem. As with most Angular apps, they are driven by services that end up talking to API endpoints on a server. Typically, these services require some form of authentication headers sent over the wire to make sure the request is valid. I had initially setup a nice pattern for this, but started to find myself drifting from the Don’t Repeat Yourself (DRY) pattern.

Luckily, Angular provides a great, singular place to put manipulation of requests prior to them being sent. These places are the HTTP interceptors.

A great reference book that goes deeper into this topic: Pro Angular 6

What are they used for?

Interceptors can be used for just about anything your application needs when it’s dealing with HTTP requests. From sending extra data over the wire to handling errors that come back from the server, my case was for handling pre-requests to send authentication headers to the API.

The interceptor structure

As mentioned, interceptors are pretty simple. They attach to an NgModule through the provider system and allow you to build up multiple if you need. They are used as an @Injectable, so writing them is nothing more than a typical class which implements a required method of intercept from the HttpInterceptor library.

1
@Injectable()
2
export class MyInterceptor implements HttpInterceptor {
3
  intercept(
4
    req: HttpRequest<any>,
5
    next: HttpHandler): Observable<any> {
6
      return next.handle(req);
7
  }
8
}

As you can see, the intercept method returns the original request that it initially intercepted. We can take this information to manipulate the request and have it continue forward.

Intercepting and manipulating

Since we do not want to lose any data from the original request, we need to clone the request to manipulate it. This is done from the handy clone method that comes off the request object.

1
@Injectable()
2
export class AuthInterceptor implements HttpInterceptor {
3
  intercept(
4
    req: HttpRequest<any>,
5
    next: HttpHandler): Observable<any> {
6
      const headers = {
7
        token: 'abc123',
8
      };
9
      const clonedRequest = req.clone({
10
        headers: new HttpHeaders(headers), 
11
      });
12
13
      return next.handle(clonedRequest);
14
  }
15
}

Now, with this handled, the token of abc123 will be sent over the wire to the API server with each request. This is great if every request needed this, but in my case, I have routes that do not require authentication; routes such as sign-up and sign-in do not have a user, so will never have authentication needs.

Note: Those with familiarity of setting headers will notice I am overriding the headers object with completely new headers. In my case, this is fine because my application is not doing anything outside of the auth headers. If by chance your application still needs the headers that were set someplace else, you can easily set the code to append new headers.

1
...
2
const clonedRequest = req.clone({
3
  headers: req.headers.append('token', 'abc123'),
4
});
5
...

Scoping requests for intercept

Skipping the intercepted routes is pretty simple through a conditional guard. Let’s change the code up to reference that now.

1
const authSkipable = ['sign-up', 'sign-in'];
2
3
@Injectable()
4
export class AuthInterceptor implements HttpInterceptor {
5
  intercept(
6
    req: HttpRequest<any>,
7
    next: HttpHandler): Observable<any> {
8
      if (authSkipable.some(str => !req.url.includes(str))) {
9
        const headers = {
10
          token: 'abc123',
11
        };
12
        const clonedRequest = req.clone({
13
          headers: new HttpHeaders(headers), 
14
        });
15
16
        return next.handle(clonedRequest);
17
      }
18
19
      return next.handle(req);
20
  }
21
}

Now, by using the array built in function some, I can setup an array of routes that I want to skip. This allows me to check against the req.url that comes through and determine if the route should be manipulated or not.

Hooking it together

Now, with the interceptor built, all it takes for your Angular application to use it is to provide it to one of your NgModules.

1
@NgModule({
2
  ...
3
  providers: [
4
    { 
5
      provide: HTTP_INTERCEPTORS, 
6
      useClass: AuthInterceptor,
7
      multi: true
8
    }
9
  ]
10
  ...
11
})
12
export class MyApp { }

The multi key value pair is important because it tells Angular the token for HTTP_INTERCEPTORS can have multiple injections with different class values. Without this, HTTP_INTERCEPTORS would only ever be injected with AuthInterceptor throughout your application.

Keeping things DRY

With the interceptor set up and the Angular application consuming it, no longer do I have to add these auth headers to each request in each service. The Angular app is smart enough to watch for a certain request, intercept it and add the appropriate headers. This helps keep the application from having multiple imports to files that could easily change over time, thus causing the need to change every use.

Filed under: Code