Interceptors only for specific Service
Asked Answered
M

4

19

I have several services in my app that point to different API URLs. Now I need to set different headers to each of these services. My question is now regarding the new interceptors in Angular 4. Is there a possibility to set one interceptor for a specific service? So each service has its specific interceptor?

Hopefully, you guys get my question.

Messinger answered 9/9, 2017 at 12:34 Comment(0)
F
17

TL:DR Answer:

No there is no way. An interceptor is designed to intercept all requests.

Long Answer:

A repository nor a request shouldn't be aware of it's interceptors it could pass. Therefore I don't agree with the solution to mark the request or to check for a specific class.

I rather like more the solution provided here: Example Angular HttpInterceptors

Basically your interceptor has to check a service (mediator pattern) if a specific header should be added.

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    console.log(JSON.stringify(req));

    const token: string = this.currentUserService.token;

    if (token) {
        req = req.clone({ headers: req.headers.set('Authorization', 'Bearer ' + token) });
    }

    if (!req.headers.has('Content-Type')) {
        req = req.clone({ headers: req.headers.set('Content-Type', 'application/json') });
    }

    req = req.clone({ headers: req.headers.set('Accept', 'application/json') });
    return next.handle(req);
}

It's a good example but be aware that it violates the single responsibility principle (setting multiple headers).

Beside that, my opinion is, that an interceptor is the wrong pattern for your problem. Also I don't see an interceptor as solution for adding a bearer token to a request. That was my use case which brought me here.

Basically I would challange your architecture and to rethink how you create the request. A solution to this problem could be following design:

Abstract-Repository

Has basic methods for get / post / put etc. which returns a HttpRequest.

Has a method called "send" which accepts a HttpRequest as a parameter.

Concret-Repository

Inherits from the abstract repository and extends the basic request function.

So for your use case you have one basic service and every specific / custom service inherits from this particular service which extends the behavior of your request.

Decorator

To take this architecture a step further (as I did) you can create a typescript decorator (not possible in all cases, e.g. when dependency injection is needed) which extends the behavior for all decorated functions. For example adding a specific header. This could look like this:

import { Observable } from 'rxjs/Observable';
import { HttpClient, HttpRequest, HttpEvent } from '@angular/common/http';
import { Injectable } from '@angular/core';

export abstract class BaseRepository<T> {
    constructor(protected client: HttpClient) {

    }

    public createGetRequest(url: string): HttpRequest<T> {
        return new HttpRequest("GET", url);
    }

    public send(request: HttpRequest<T>): Observable<HttpEvent<T>> {
        return this.client.request(request);
    }
}

@Injectable()
export class NormalServiceRepository extends BaseRepository<any> {

    constructor(protected client: HttpClient) {
        super(client);
    }

    public get(url: string): Observable<any> {
        const baseRequest = super.createGetRequest(url);

        baseRequest.headers.set('Content-Type', 'application/json');

        return this.send(baseRequest);
    }
}



@Injectable()
export class AuthServiceRepository extends BaseRepository<any> {

    constructor(protected client: HttpClient) {
        super(client);
    }

    @AcceptsJson()
    @SendsJson()
    @ForwardCredentials()
    public createGetRequest(url: string): HttpRequest<any> {
        return super.createGetRequest(url);
    }

    public get(url: string): Observable<any> {
        const baseRequest = super.createGetRequest(url);
        return this.send(baseRequest);
    }
}

That should give you a basic picture how the architecture would look like.

More about decorators

TypeScript Decorators

Example Implementations

Fishbein answered 31/5, 2018 at 13:51 Comment(0)
H
6

There actually is a way to have an interceptor for a specific service. Notice the emphasis, because it's not really an implementation of the HttpInterceptor interface.

TL:DR - jump to examples at the bottom.

The HttpClient actually only converts the input through all the possible methods into a single structure called HttpRequest, which is then being passed to an HttpHandler implementation. That, as the name suggests, is responsible for handling the HTTP requests.
Among HttpHandler implementations you'd find e.g. HttpInterceptingHandler (runs the chain of interceptors) or HttpXhrBackend (inherited through the HttpBackend class, this handler is the last as it actually sends the XHR request). If you take a look in the source code of the former class, you'll see that it's actually depending on the latter (indirectly, under the HttpBackend token, HttpXhrBackend is provided as default implementation). So just like the interceptors, Angular is chaining the handlers through DI, where the last one is the backend executing the request.

What you can do is adding another handler to this chain of handlers, ideally as the first one. For that you will have to define a class extending HttpHandler, injecting the first implementation of HttpHandler (by first I mean the implementation, which is provided under the HttpHandler token) and just call-through in the handle method after you're done.

At last, since the HTTP client is an Angular service, it may be shared among more components, directives, services etc., so you will want to create your own instance to avoid that and keep your interceptor isolated. The HttpClient constructor requires an HttpHandler instance, where you pass your own implementation.

See my simple example for handling authentication in such handler:

@Injectable()
export class AuthHandler extends HttpHandler {
    constructor(
        private readonly auth: AuthService,  // custom service providing the auth token
        private readonly next: HttpHandler   // injects the "default" handler -> HttpInterceptingHandler
    ) {
        super();
    }

    /** @override */ handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
        // do whatever you need on the request
        // because of immutability cloning is required
        const clone = req.clone({
            setHeaders: { 'Authorization': 'Bearer ' + auth.getToken() }
        });
        // pass the request to the next handler, eventually ending up in HttpXhrBackend
        return this.next.handle(clone);
    }
}

And the usage of the handler in that one specific service:

export class HeroService {
    protected readonly http: HttpClient;

    constructor(auth: AuthHandler) {
        // create your own instance with you custom handler
        this.http = new HttpClient(auth);
    }

    async getHero(id: string): Hero {
        // every request made through such client goes to the handler for processing
        await this.http.get<Hero>(`/api/heroes/${id}`).toPromise();
    }
}

This code is working for me on Angular 8.
I hope this helps other lost souls searching for a solution.

EDIT 2023-06-20: Fixed link to Angular code in GitHub.

Healey answered 7/9, 2020 at 11:58 Comment(1)
Also worth mentioning this is also testable, as only the last layer (HttpXhrBackend) is replaced with a testing class. I just verified it in my code.Healey
F
1

Once a request/response is fired with the new HttpClient, all interceptors will be called. One thing you can do, is marking your request, so you set the right header in the interceptor that is designed to handle that request.

Funnyman answered 9/9, 2017 at 12:42 Comment(3)
Thanks for your answer. How I could mark my request? Thanks for your help.Messinger
You could for instance create separate request type classes, which extend HttpRequest, so that you can filter by the class type. You could add a special Header by which you filter. You could add a hash tag to the end of the URL. Just some examples.Funnyman
But how to recognize response for such tagged requests?Enunciate
S
1

A bit late to this question but this works for us. Yes, it goes into the interceptor but a simple condition can make it not execute.

 intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // Do not execute interceptor
    if (some condition)
      return next.handle(req);

    // Else.. do your thing and go next...
    // Add headers, modify reqyest, etc..
    return next.handle(req);
  }
Sorrel answered 19/1, 2020 at 14:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.