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
- fix(common): allow interceptors to inject HttpClient Previously, an interceptor attempting to inject HttpClient directly would receive a circular dependency error, as HttpClient was constructed via a... — committed to alxhub/angular by alxhub 7 years ago
- fix(common): allow interceptors to inject HttpClient Previously, an interceptor attempting to inject HttpClient directly would receive a circular dependency error, as HttpClient was constructed via a... — committed to alxhub/angular by alxhub 7 years ago
- fix(common): allow interceptors to inject HttpClient Previously, an interceptor attempting to inject HttpClient directly would receive a circular dependency error, as HttpClient was constructed via a... — committed to alxhub/angular by alxhub 7 years ago
- fix(common): allow interceptors to inject HttpClient Previously, an interceptor attempting to inject HttpClient directly would receive a circular dependency error, as HttpClient was constructed via a... — committed to alxhub/angular by alxhub 7 years ago
- Forced logout if TWT token des not pass server validation. Removed force logout from various http calls error handlers, now we have just one place we do this: AppComponent. pullNotofications method, s... — committed to grassrootza/grassroot-frontend by beegor 6 years ago
- fix(common): allow HttpInterceptors to inject HttpClient Previously, an interceptor attempting to inject HttpClient directly would receive a circular dependency error, as HttpClient was constructed v... — committed to alxhub/angular by alxhub 7 years ago
- fix(common): allow HttpInterceptors to inject HttpClient (#19809) Previously, an interceptor attempting to inject HttpClient directly would receive a circular dependency error, as HttpClient was cons... — committed to angular/angular by alxhub 7 years ago
- fix(common): allow HttpInterceptors to inject HttpClient (#19809) Previously, an interceptor attempting to inject HttpClient directly would receive a circular dependency error, as HttpClient was cons... — committed to jbogarthyde/angular by alxhub 7 years ago
- fix(common): allow HttpInterceptors to inject HttpClient (#19809) Previously, an interceptor attempting to inject HttpClient directly would receive a circular dependency error, as HttpClient was cons... — committed to leo6104/angular by alxhub 7 years ago
I resolved simply not setting authService in constructor but getting in the intercept function.
My code is very similar to @cyrilletuzi, I tried @Toxicable solution, I have no more cyclic dependency but I get:
@tytskyi I tried that and didn’t work
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:
You may also try a little hack to instatiate the service, like:
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, andAuthInterceptor
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?
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
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
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,
toprivate http: HttpClient
I get this cycle errorCannot instantiate cyclic dependency! InjectionToken_HTTP_INTERCEPTORS (“[ERROR ->]”) any ideas how can I make it work?
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 (likelogin
), but theAuthInterceptor
does not use them, it just needs theAuthService
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 ofHttpClient
there.I use this solution in my
auth.interceptor.ts
file:@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
orng 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
andng serve --prod
(orng build --prod
to build the client app) will produce the error. Weird thing is that it will mentionapp.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
andpackage-lock.json
first) Then I’ve replaced the old code in my interceptor:with what it should be:
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 running5.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:
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 😃
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:
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
@bogomips https://github.com/angular/angular/issues/18224#issuecomment-334813773
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