HttpInterceptor->Service->HttpClient Cyclic Dependency
Asked Answered
E

7

19

So I have my authentication service, AuthService, that basically has two methods, one that gets a new token from the server, given a username and a password, and one that retrieves the current stored token and refreshes the tokens during the process when necessary. Both obviously rely on HttpClient. That is, AuthService has a dependency on HttpClient. Let's keep that in mind.

Another "service" is an HttpInterceptor that I want to intercept all outgoing requests other than those made by AuthService to add the Authorization header (it's getting dirty now). And to make up that header, we need a token, which we get from AuthService. That is, AuthInterceptor (the name of my interceptor) has a dependency on AuthService. And as far as I know, HttpClient has a dependency on all HTTP_INTERCEPTORS.

So the scenario is as follows: Cyclic Dependency

Any ideas or suggestions on how to break that circle? Is there any way to make AuthService's HttpClient independent of AuthInterceptor? Or is it a bad idea? (Another third function will be added to AuthService for logging the user out, whose requests shall be intercepted and have the Authorization header added to them as well)

So far I found a similar issue but the workaround suggested there doesn't solve my problem, now I get infinite recursion during the bootstrapping process before any requests are sent. I've handled the case of login and token refresh requests being intercepted to avoid this so this is not the issue here as far as I know. Here's a plunk with an overview of my code.

Excerpt from the plunk:

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  private auth: AuthService;

  constructor(inj: Injector) {
    this.auth = inj.get(AuthService);
  }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // Ignore if login or refresh request
    if (req.url.includes('login')) {
      return next.handle(req);
    }

    console.log('Intercepting request...');
    return this.auth.getToken().map(token => {
      const authHeader = 'Bearer ' + token;
      const authReq = req.clone({setHeaders: {Authorization: authHeader}});
      return authReq;
    }).concatMap(newReq => next.handle(newReq));
  }
}
Estipulate answered 20/7, 2017 at 11:26 Comment(0)
P
5

Update 08/02/2018 - angular 5.2.3

Just an update on this: this was fixed in angular 5.2.3

https://github.com/angular/angular/blob/master/CHANGELOG.md#bug-fixes-2

So you can directly inject services that depend on HttpClient in HttpInterceptors

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

    constructor(private auth: AuthService) 
Pearson answered 8/2, 2018 at 10:53 Comment(4)
Not sure... it still giving the error Cannot instantiate cyclic dependency! HTTP_INTERCEPTORSSheaves
Weird, for me upgrading to 5.2.3 fixed the issue right awayPearson
Oops, sorry! I was running 5.2.0. Just update to the last version and it's working. Thank you.Sheaves
I'm still/again running into this with Angular 6.1.10. I needed to instantiate HttpClient myself inside the AuthService with new HttpClient(httpBackend) using an injected httpBackend.Fame
F
10

I was running into the same or a similar issue using Angular 6.1.10. I simply instantiated HttpClient myself inside the service that need to be injected into an HttpInterceptor:

@Injectable()
export class AuthService {

  private http: HttpClient;

  constructor(httpBackend: HttpBackend) {
    this.http = new HttpClient(httpBackend);    
  }
}

That broke the infinite loop issue in my case.

Fame answered 17/10, 2018 at 13:28 Comment(1)
Thanks ! still works on Angular 15Conversable
D
6

Try setting this.auth with a timeout:

constructor(private injector: Injector) {
  setTimeout(() => {
    this.auth = this.injector.get(AuthService);
  })
}

The bug report you have linked to has since been updated, with an alternative workaround (retrieving AuthService in the intercept function / and not setting it in the constructor): https://github.com/angular/angular/issues/18224#issuecomment-316957213

Deter answered 24/7, 2017 at 12:39 Comment(1)
This problem still persists for me even if I set AuthService inside of the intercept function and use injector.Poulin
P
5

Update 08/02/2018 - angular 5.2.3

Just an update on this: this was fixed in angular 5.2.3

https://github.com/angular/angular/blob/master/CHANGELOG.md#bug-fixes-2

So you can directly inject services that depend on HttpClient in HttpInterceptors

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

    constructor(private auth: AuthService) 
Pearson answered 8/2, 2018 at 10:53 Comment(4)
Not sure... it still giving the error Cannot instantiate cyclic dependency! HTTP_INTERCEPTORSSheaves
Weird, for me upgrading to 5.2.3 fixed the issue right awayPearson
Oops, sorry! I was running 5.2.0. Just update to the last version and it's working. Thank you.Sheaves
I'm still/again running into this with Angular 6.1.10. I needed to instantiate HttpClient myself inside the AuthService with new HttpClient(httpBackend) using an injected httpBackend.Fame
B
2

Just ran into this same error. In my case I was injecting a translateService into my interceptor, and my translateService injects DateAdapter into it. I was actually missing the module import in order for DateAdapter to work (MatNativeDateModule), and for some reason that missing import in my module was causing the circular dependency.

Hope this helps someone else, and maybe someone can help me understand why a missing import would cause a circular dependency? It doesn't really make sense to me.

UPDATE:

I just figured out why this was happening. Since my interceptor uses my translateService (which is actually just transloco under the hood), and the transloco-root-module ALSO uses HttpClient for the getTranslation function, if that request to get the json file failed for whatever reason then the interceptor would get called. So what is happening is:

  • transloco depends on HttpClient
  • HttpClient depends on HttpInterceptor
  • HttpInterceptor depends on transloco

circular dependency

This took quite a while for me to debug since it was very rare that loading the transloco json file would fail. But just in case it does happen, I used HttpBackend in my transloco-root-module instead to create a new HttpClient (HttpClients that are created this way do not use any interceptors, so my interceptor will never get called from transloco)

export class TranslocoHttpLoader implements TranslocoLoader
{
  private httpClient: HttpClient;

  constructor(httpBackend: HttpBackend)
  {
    this.httpClient = new HttpClient(httpBackend);
  }

  public getTranslation(lang: string)
  {
    return this.httpClient.get<Translation>(`/assets/i18n/${lang}.json`);
  }
}
Bradawl answered 29/9, 2022 at 0:52 Comment(0)
P
0

My problem was using the HttpClient in the service constructor that was injected into the HttpInterceptor.

export class SettingsService {
    constructor(
        private http: HttpClient,
    ) {
        this.http.get(...).subscribe() => {
            ...
        });
    }
}

export class AppInterceptor implements HttpInterceptor {
    constructor(
        private settingsS: SettingsService,
    ) { }
    ...
    ...
}
Placatory answered 3/6, 2021 at 7:23 Comment(0)
S
0

I don't know if it may help or not, but I got the same issue and I realized that I missed importing the "HttpClientModule" in "app.module.ts"

Snowfield answered 11/11, 2022 at 1:42 Comment(0)
O
0

I've just ran into the exact same issue, read the (now closed) thread on Angular github and came up with my own solution; maybe there is something wrong with it which I'm unaware of, but it looks pretty clean to me:

Why not treat your AuthInterceptor as a stateful service? You can always just add a method allowing setting the token in the AuthInterceptor and then set it from your AuthService:

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
    private token: string;
   
    setToken(token: string) {
        this.token = token;
    }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        ...
    }
}

This way, AuthService requires HttpClient and AuthInterceptor and there is no circular dependency. Just remember to provide the same instance of AuthInterceptor for both HTTP_INTERCEPTORS and AuthInterceptor injection tokens with useExisting:

providers: [
    AuthInterceptor,
    {
        provide: HTTP_INTERCEPTORS,
        useExisting: AuthInterceptor,
        multi: true
    },
Onder answered 10/1, 2023 at 15:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.