angular: Cyclic dependency error with HttpInterceptor

I’m submitting a…


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

Trying to do the same example of AuthInterceptor like in official doc, to add a Authorization header on all requests.

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private auth: AuthService) {}
 
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    const authHeader = this.auth.getAuthorizationHeader();
    const authReq = req.clone({headers: req.headers.set('Authorization', authHeader)});
    return next.handle(authReq);
  }
}

Problem : with real code, the AuthService needs to request the server on login, signin, etc., so it itself inject HttpClient.

@Injectable()
export class AuthService {

  constructor(private http: HttpClient) {}

}

It creates this fatal error in console :

Uncaught Error: Provider parse errors:
Cannot instantiate cyclic dependency! InjectionToken_HTTP_INTERCEPTORS ("[ERROR ->]"): in NgModule AppModule in ./AppModule@-1:-1

Expected behavior

Should work, as it’s a very common case. Otherwise interceptors are limited to use services without Http.

Environment


Angular version: 4.3.0

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 155
  • Comments: 88 (8 by maintainers)

Commits related to this issue

Most upvoted comments

I resolved simply not setting authService in constructor but getting in the intercept function.


  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // Get the auth header from the service.
    const auth = this.inj.get(AuthenticationService);
    const authToken = auth.getAuthorizationToken();
    ...
   }

My code is very similar to @cyrilletuzi, I tried @Toxicable solution, I have no more cyclic dependency but I get:

ERROR Error: Uncaught (in promise): RangeError: Maximum call stack size exceeded
RangeError: Maximum call stack size exceeded
    at _createProviderInstance$1 (core.es5.js:9480)
    ...   

@tytskyi I tried that and didn’t work

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

works though

https://plnkr.co/edit/8CpyAUSJcCiRqdZmuvt9?p=preview

@will-copperleaf Which example have you tried?

You should be able to do this for example:

import { Injectable, Injector } from '@angular/core';
import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { AuthService } from '../shared/auth.service';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  private authService: AuthService;

  constructor(
    private injector: Injector
  ) { }

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

    this.authService = this.injector.get(AuthService); // get it here within intercept

    const authRequest = request.clone({
      headers: request.headers.set('Authorization', this.authService.getHeaders())
    });

    return next.handle(authRequest);
  }

}

You may also try a little hack to instatiate the service, like:

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

This way you won’t get the max call stack exceeded error.

I’m having the same issue, and seems there’s still no satisfying answer provided above. This shouldn’t be closed.

This is interesting,

I thought that having service like AuthService, which manages token accessibility and performs login and refresh requests it’s a common use case.

So basically here is the scenario. We have AuthService which depends on HttpClient (obviously). It has few methods: getAccessToken, getRefreshToken, login, refreshSession and logout.

Also in addition we need to have AuthInterceptor, which will add token to the request, if token exists. And obviously it depends on AuthService, cause service has access to the token.

So here’s the problem: HttpClient depends on HTTP_INTERCEPTORS (basically it’s AuthInterceptor), AuthInterceptor depends on AuthService, and AuthService depends on HttpClient. And Cyclic error.

So this common use case is not possible in the current implementation.

But why angular team couldn’t implement interceptors in the similar way how the first angular interceptors work? E.g. to have extra store service, using which one we can register new interceptors, e.g. httpInterceptors.push(interceptor)?

I wrote similar library called ng4-http, which implements similar approach to the first angular. It uses additional service which stores registered interceptors. And in this case we can inject any kind of services there and use them properly. Current library uses old http module.

So the question is, am I wrong? If yes, then where ?

This is a true cyclic dependency, and @Toxicable’s example is the correct way to handle it.

However, I’d be concerned if the AuthService was making its own HTTP requests, and AuthInterceptor added the header to every request (as it’s written) without checking the details of that request. That means that a request will go through the interceptor, which goes to the auth service, which makes a request, which goes through the interceptor, and it recurses infinitely.

Will it work like this?

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(@Inject(forwardRef(() => AuthService)) private auth: AuthService) {}
 
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    const authHeader = this.auth.getAuthorizationHeader();
    const authReq = req.clone({headers: req.headers.set('Authorization', authHeader)});
    return next.handle(authReq);
  }
}

For info, for now I’ve separated in two services, one for login/signin/… (which uses HttpClient) and one just for managing the AuthToken (no Http).

But going further, I added a error handler to catch 401 errors (when the token is invalid). But in such case, I need to call logout, so I need the service with HttpClient, and thus I fall in the initial problem again, so it is very problematic.

That’s the final interceptor with some logging functionality and keep loading status for loader animation

import {Injectable, Injector} from "@angular/core";
import {HttpEvent, HttpHandler, HttpInterceptor, HttpResponse} from "@angular/common/http";
import {HttpRequest} from "@angular/common/http";
import {Observable} from "rxjs/Observable";
import {SiteService} from "../services/site.service";
import {Router} from "@angular/router";
import {LoadingService} from "../../components/loading/loading.service";
import {AuthenticationService} from "../services/authentication.service";

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

    constructor(private router: Router,
                private siteService: SiteService,
                private loadingService: LoadingService,
                private injector: Injector) {
    }



    private fixUrl(url: string) {
        if (url.indexOf('http://') >= 0 || url.indexOf('https://') >= 0)
            return url;
        else
            return this.siteService.apiDomain() + url;
    }

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

        const clonedRequest = req.clone({
            headers: req.headers.set('Authorization', 'Bearer ' + localStorage.getItem('JWToken')),
            url: this.fixUrl(req.url)
        });

        let authenticationService = this.injector.get(AuthenticationService);

        this.loadingService.start();
        const started = Date.now();
        return next.handle(clonedRequest)
            .do(event => {
                if (event instanceof HttpResponse) {

                    const elapsed = Date.now() - started;
                    console.log('%c Request for ' + this.fixUrl(req.urlWithParams) + ' took ' + elapsed + ' ms.', 'background: #222; color: yellow');
                }
            })
            ._finally(() => {
                this.loadingService.stop();
            })
            .catch((res) => {
                if (res.status === 401 || res.status === 403) {
                    this.loadingService.start();
                    return authenticationService.refreshToken().flatMap((data: any) => {
                        this.loadingService.stop();
                        if (data.token !== '') {
                            localStorage.setItem('currentUser', JSON.stringify(data.user));
                            localStorage.setItem('currentUserPermissions', JSON.stringify(data.permissions));
                            localStorage.setItem('JWToken', data.token);
                        } else {
                            localStorage.removeItem('currentUser');
                            localStorage.removeItem('currentUserPermissions');
                            localStorage.removeItem('JWToken');
                            this.router.navigate(['./auth/login']);
                            return Observable.throw(res);
                        }
                        let clonedRequestRepeat = req.clone({
                            headers: req.headers.set('Authorization', 'Bearer ' + localStorage.getItem('JWToken')),
                            url: this.fixUrl(req.url)
                        });
                        return next.handle(clonedRequestRepeat).do(event => {
                            if (event instanceof HttpResponse) {

                                const elapsed = Date.now() - started;
                                console.log('%c Request for ' + req.urlWithParams + ' took ' + elapsed + ' ms.', 'background: #222; color: yellow');
                            }
                        });
                    })
                } else {
                    return Observable.throw(res);
                }

            });

    }
}

And don’t forget…

Another important thing for those who will use this interceptor for their project is to set headers to the refresh token response at least some seconds.

->header(‘Cache-Control’, ‘public, max-age=45’) ->header(‘Expires’, date('D, d M Y H:i:s ', time() + 45).‘GMT’);

Also… And this is my loadingService


@Injectable()
export class LoadingService {

    public count = 0;

    constructor() { }

    start(): void {
        this.count++;
    }

    stop(): void {
        this.count--;
    }


}

Agreed @will-copperleaf. I still view this as a design problem with HttpClient/Interceptors in general. Interceptors needing to make requests themselves is a fairly common case (particularly when they’re being used for authentication, as I suspect is the case for most here). Injecting the Injector and requesting services ad-hoc seems a bit hacky.

I use HttpClient and I create this interceptor to add the jwt token. Everyting work perfect but I have a bad practise. I use Http inside the HttpClient Interceptor. If I change

private http: Http, to private http: HttpClient I get this cycle error

Cannot instantiate cyclic dependency! InjectionToken_HTTP_INTERCEPTORS (“[ERROR ->]”) any ideas how can I make it work?

import {Injectable} from "@angular/core";
import {HttpEvent, HttpHandler, HttpInterceptor} from "@angular/common/http";
import {HttpRequest} from "@angular/common/http";
import {Observable} from "rxjs/Observable";
import {Http} from "@angular/http";
import {SiteService} from "../services/site.service";
import {Router} from "@angular/router";

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

constructor(
    private http: Http,
    private router: Router,
    private siteService: SiteService
) {}

refreshToken() {
    return this.http.get(this.siteService.apiDomain() + '/api/token?token=' + localStorage.getItem('JWToken'), {})
        .map((response: any) => {
            let data = response.json();
            return {
                token: data.token,
                permissions: data.permissions,
                user: data.user,
            };
        })
}

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const clonedRequest = req.clone({
        headers: req.headers.set('Authorization', 'Bearer ' + localStorage.getItem('JWToken'))
    });

    return next.handle(clonedRequest).catch((res) => {

        if (res.status === 401 || res.status === 403) {
            return this.refreshToken().flatMap((data) => {
                if (data.token !== '') {
                    localStorage.setItem('currentUser', JSON.stringify(data.user));
                    localStorage.setItem('currentUserPermissions', JSON.stringify(data.permissions));
                    localStorage.setItem('JWToken', data.token);
                } else {
                    localStorage.removeItem('currentUser');
                    localStorage.removeItem('currentUserPermissions');
                    localStorage.removeItem('JWToken');
                    this.router.navigate(['./auth/login']);
                    return Observable.throw(res);
                }
                const clonedRequestRepeat = req.clone({
                    headers: req.headers.set('Authorization', 'Bearer ' + localStorage.getItem('JWToken'))
                });
                return next.handle(clonedRequestRepeat);
            })
        } else {
            return Observable.throw(res);
        }

    });

}
}

Another important thing for those who will use this interceptor for their project but irrelevant to the current problem is to set headers to the refresh token response at least some seconds.

->header(‘Cache-Control’, ‘public, max-age=45’) ->header(‘Expires’, date('D, d M Y H:i:s ', time() + 45).‘GMT’);

No, the AuthService contains methods which need to do some HTTP requests (like login), but the AuthInterceptor does not use them, it just needs the AuthService to get the token.

If you have this problem on Ionic, you might find yourself struggling with Maximum Call Stack Size Exceeded. Just do the inj.get(…) inside platform.ready().then(() => […]); inside of the Interceptor!

This example also works for me, but i worred because http in the future may be deprecated and also is not good for the app to inject 2 different libraries for the same thing.

If anyone find a solution, please answer…

Is it something that the Angular team is actually looking to fix so that people don’t have to inject stuff into other stuff that are injected in some other stuff to circumvent the issue or is it somewhere at the bottom of a huge backlog of stuff that will most probably never be looked at, so we can resort to our own solutions to this for ever? If the second thing is true, probably a note in the documentation that says that interceptors are not supposed to accept any async dependencies that depend on HttpClient would be helpful.

i added {deps:Http] in main module and it works for me, but i don’t why is need

providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthTokenInterceptor, multi: true, deps: [Http] }],

@perusopersonale that stack overflow error means that your circular dependency has been resolved at build time but is still very much present at runtime, you’re making a new request from within the interceptor which also goes through the interceptor, and that repeats forever.

I suggest using next.handle() to make the dependent request instead of trying to get ahold of HttpClient there.

I use this solution in my auth.interceptor.ts file:

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

  if (req.url.includes('/token')) {
    return next.handle(req);
  }
  this.tokenService = this.injector.get(TokenService);
...
}

@melicerte sorry for the late response, didn’t realize I had to pay my hosting provider. It was weird not getting email notifications for a few days, until I realized why 😃

On topic: It turns out, if you have a Universal app, you still get the error. Of course I didn’t notice this until I decided to create a new app, from scratch 😄 I don’t even have to build the Universal app, it just needs to be there. Simply running ng serve --prod or ng build --prod throws the error (this builds the first app in the apps array, which is the client in my case).

@alxhub I’ve made a repo here to reproduce it. Simply running npm install and ng serve --prod (or ng build --prod to build the client app) will produce the error. Weird thing is that it will mention app.server.module, even though you’re just building the client app. Not sure if this is an issue for the CLI rather than Angular?

Angular CLI: 1.6.8 Angular: 5.2.4 Node: 8.9.4 OS: win32 x64

I see this was closed with the release of 5.2.3 (and it is mentioned in the changelog). But it still doesn’t work. I’ve just updated to 5.2.3 (nuked node_modules and package-lock.json first) Then I’ve replaced the old code in my interceptor:

  constructor(
    private injector: Injector
  ) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authService = this.injector.get(AuthService);
    const token = authService.getToken();
    const reqCopy = req.clone({
      headers: req.headers.append('Authorization', `Bearer ${token}`)
    });
    return next.handle(reqCopy);
  }

with what it should be:

  constructor(
    private authService: AuthService
  ) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = this.authService.getToken();
    const reqCopy = req.clone({
      headers: req.headers.append('Authorization', `Bearer ${token}`)
    });
    return next.handle(reqCopy);
  }

And I get the cyclic dependency error when I try to build with the --prod flag.

I’ve tried @perusopersonale 's solution and it doesn’t work. I get the Maximum call stack size exceeded error. I’m running 5.0.0. Has anyone else got this to work in version 5?

@jdhines Yes, you must pass arguments describing your request. That class have few overloads

https://github.com/angular/angular/blob/c8a1a14b87e5907458e8e87021e47f9796cb3257/packages/common/http/src/request.ts#L129-L164

Most commonly you would use something like this:

const myRequest = new HttpRequest('POST', 'url/here', payload, options) // post
const myRequest2 = new HttpRequest('GET', 'url/here', options) // get

I resolved this similar to @iget-master by refactoring AuthService into a helper service that is independent of HttpClient (AuthHelper: a class that is only concerned with getting/setting tokens in local storage as well as caching failed requests) and the original AuthService, which now only contains HttpClient-dependent functions.

The original dependency graph: HttpInterceptors --> AuthService --> HttpClient --> HttpInterceptors

The fixed dependency graph: HttpInterceptors --> AuthHelper <-- AuthService --> HttpClient -->HttpInterceptors

This not only fixes the cyclical dependency, but makes the code cleaner by better separating concerns. AuthService was concerned about both the implementation of locally storing tokens and the implementation of requesting/processing tokens from the application server. Now it only does the latter and delegates the former responsibility to the helper class.

This doesn’t resolve the use-case of wanting to make an http request in the interceptor function (see @serhiisol 's comment), but that seems like a very dangerous use-case. What do you expect will happen when your interceptor intercepts its own request???

For me it works like this.

this.injector.get(AuthService).logout();

Hi, for me work like this

`export class SecurityFactory implements HttpInterceptor { private authService: AuthService; constructor(@Inject(forwardRef(() => Injector)) private injector: Injector) { } intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { this.authService = this.injector.get(AuthService); return next.handle(req).do(event => { if (event[‘status’] === 401) { this.authService.logout(‘expired’); } }); } }

export function tokenGetter() { return sessionStorage.getItem(‘token’); }

@NgModule({ imports: [ JwtModule.forRoot({ config: { tokenGetter: tokenGetter, skipWhenExpired: true, whitelistedDomains: [‘localhost:4200’] } }) ], providers: [ {provide: HTTP_INTERCEPTORS, useClass: SecurityFactory, multi: true} ] }) export class AuthModule { }`

Worth noting that if you need to send http request from the interceptor to get some data(i.e revalidation logic) and you don’t want to use bare xhr/fetch you can always use class-interface HttpBackend to omit interceptors and send request that way. Plus it works when mocking backend for tests 😃

import { Inject, Injectable, InjectionToken } from '@angular/core';
import { HttpBackend } from '@angular/common/http';
import {
  HttpInterceptor,
  HttpEvent,
  HttpHandler,
  HttpRequest,
  HttpEventType,
  HttpErrorResponse,
} from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/retryWhen';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import 'rxjs/add/observable/of';

export const MY_REQUEST = new InjectionToken<HttpRequest<any>>('MY_REQUEST');

@Injectable()
export class MyInterceptor implements HttpInterceptor {
  constructor(
    @Inject(MY_REQUEST) private myRequest: HttpRequest<any>,
    private backend: HttpBackend
  ) {}

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    return next.handle(req).retryWhen(errors => {
      return errors.mergeMap((error: HttpEvent<any>) => {
        if (error instanceof HttpErrorResponse && error.status === 401) {
          return (
            this.backend
              .handle(this.myRequest)
              // Get response.
              .filter(event => event.type === HttpEventType.Response)
              // Retry on success.
              .mergeMap(() => Observable.of(null))
              // Stop and throw on error.
              .catch(() => Observable.throw(error))
          );
        }

        // Throw error otherwise.
        return Observable.throw(error);
      });
    });
  }
}

Using HttpHandler may be problematic in some cases for 3rd party library because user can add more interceptors which can modify your request.

Just to remind if someone does not know. RefreshToken can be retrieved only while main token is not expired. You can call token endpoint and post refresh token to obtain new accessToken (e.g. 1 hour before main token os expired).The problem is the following. In such case our method getAccessToken() will return observable result because it exposes http end obtaines new token using refresh token if necessary. So how would you handle the following: handle cyclic dependency issue and inject observable to interceptor?

This also works:

import { Injectable } from '@angular/core';
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { AuthService } from '../auth/auth.service';

@Injectable({
  providedIn: 'root'
})
export class JwtInterceptorService implements HttpInterceptor {

  constructor(private authService: AuthService) {
  }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // Add authorization header with bearer token if available.
    const token: string = this.authService.tokenValue;
    if (token) {
      request = request.clone({
        setHeaders: {
          Authorization: `Bearer ${token}`
        }
      });
    }

    return next.handle(request).pipe(catchError((error: HttpErrorResponse) => {
      // Auto logout if 401 response returned from api
      if (error.status === 401) {
        this.authService.unAuthUser();
      }
      return throwError(error);
    }));
  }
}

I’m not sure why noone else has pointed this out but apparently this is fixed for Angular 6 by means of introducing an HttpInterceptingHandler which itself lazily loads the interceptors and avoids the circular dependency.

I got stuck with a similar cyclic dependency and resorted to do the workaround of manually injecting the service https://stackoverflow.com/questions/49240232/getting-a-cyclic-dependency-error

Just adding a comment that this is still an issue with version 5.1.1

So basically if my authService needs to do an http request to retrieve both the token and the refresh token and then store those in localStorage, how would you propose to implement this? I see in the above examples that people inside the intercept method add the headers from the authService or directly add the token from localStorage(which means you already have a token). What if my getHeaders() method is asynchronous?

From what I understand, there is no way to exclude specific requests from the interceptors so that they don’t get intercepted, there is no way to load the interceptors for a specific component or module (they have to be defined in the app module).

I have code that is quite similar to the examples posted here, and I’ve been encountering the same issue. In fact, it seems extremely close to what @cyrilletuzi is trying to do, I’m just trying to get the value of a token property I have set in an AuthService, I’m not trying to make use of it any other way. Just access that in the interceptor. Adding the service to the constructor in the interceptor gives me the cyclic dependency error, basically what @mixalistzikas got, down to the message, while trying the solution @Toxicable provided seems to give me the same issue @perusopersonale got, i.e. a problem with too much recursion, and the app crashing.

Eventually just decided to access the token directly from session storage where I had it stored, rather than retrieve it from the service, but the fact that this issue seems to be occurring when there doesn’t seem to be any real cyclic dependency seems off. The workaround I described works for now for me, but definitely many situations where you would need to access a service somewhere, and that doesn’t seem possible in this manner