23

I am receiving the following strange dependency injection behavior when using custom HttpInterceptors in angular 5+.

The following simplified code works fine:

    export class AuthInterceptor implements HttpInterceptor {
        constructor(private auth: AuthService) {}

        intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
            const token = this.auth.getToken();
            return next.handle(req);
        }
    }
    export class AuthService {
        token: string;
        constructor() {
          console.log('AuthService.constructor');
        }
    }

HOWEVER....

When the AuthService has 1 or more dependencies on its own e.g.

   export class AuthService {
      token: string;
      constructor(private api: APIService) {
         console.log('AuthService.constructor');
      }
   }

angular is trying to repeatedly create new instances of AuthService until I receive the following errors:

The log is displaying the AuthService.constructor message ~400 times

and

Cannot instantiate cyclic dependency! HTTP_INTERCEPTORS ("[ERROR ->]"): in NgModule AppModule

and

app.component.html:44 ERROR RangeError: Maximum call stack size exceeded

I then tried injecting the service using the Injector class -

 export class AuthService {
      token: string;
      api: APIService;
      constructor(private injector: Injector) {
         this.api = this.injector.get(APIService);
         console.log('AuthService.constructor');
      }
   }

but getting the same error (maximum call stack size).

The APIService is a simple service that only injects the HttpClient in its constructor.

@Injectable()
export class APIService {
    constructor(private http: HttpClient) {}
}

Lastly, when I inject the AuthService into the Interceptor using the Injector, the error disappears but the AuthService is being instantiated 200+ times:

export class AuthInterceptor implements HttpInterceptor {
    auth: AuthService;
    constructor(private injector: Injector) {}
    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
           this.auth = this.auth || this.injector.get(AuthService);
           const token = this.auth.getToken();
           return next.handle(req);
        }
    }

Looking at the official documentation and other example it seems as it is technically possible to inject services into the Http Interceptors. Is there any limitation or any other setup that might be missing?

dev7
  • 6,259
  • 6
  • 32
  • 65
  • @SurenSrapyan updated the question – dev7 Jan 17 '18 at 05:36
  • can you reproduce it with stackblitz demo? – Max Koretskyi Jan 17 '18 at 06:29
  • I got a similar issue with the same design of auth service coupled with an interceptor. In my case, i found the cause is that I try to start a http request in auth service's constructor. – Edward Liang Jun 07 '18 at 07:00
  • For other stumbling upon this, try to add this decorator to your interceptor `@Injectable({ providedIn: 'root' }) export class HttpErrorInterceptor implements HttpInterceptor {`, it worked for me. – Diosney Jan 03 '19 at 20:01

8 Answers8

12

Update on end of January 2018

Angular Team resolved this issue in Angular 5.2.3 released 31 January 2018. After updating angular version you will be able to inject services that use HTTPClient as normal in constructor

Bug Fixes

common: allow HttpInterceptors to inject HttpClient (#19809) (ed2b717), closes #18224

from Angular changelog

Gwidon
  • 121
  • 4
8

So it turns out that if the service you inject into the Http Interceptor has a dependency on HttpClient, this leads to a cyclic dependency.

Since my AuthService was a mix of all different logics (login/out, routing the user, saving/loading tokens, making api calls), I separated the part needed for the interceptors into its own service (just the user credentials & tokens) and now injecting it successfully into the Interceptor.

export class AuthInterceptor implements HttpInterceptor {
    constructor(private credentials: CredentialsService) {}
    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        const token = this.credentials.getToken();
        const api_key = this.credentials.getApiKey();
    }
}

export class CredentialsService {
    token: string;
    user: IUser;
    constructor(private http: HttpClient) {
        this.loadCredentialsFromStorage();
    }
}

This seems to work fine. Hope this helps someone.

dev7
  • 6,259
  • 6
  • 32
  • 65
  • 1
    the code is contradicting your first line, in your code you injected a service which is dependent on HttpClient so how does this resolve the issue – sarfrazanwar Apr 17 '22 at 17:27
4

You need to add the auth service as a dependency of the interceptor.

providers: [ 
{
  provide: HTTP_INTERCEPTORS,
  useClass: HttpConfigInterceptor,
  multi: true,
  deps: [AuthService]
}
Benjineer
  • 1,530
  • 18
  • 22
3

you need to add Injector into constructor and inject AuthService via injector

export class AuthInterceptor implements HttpInterceptor {
            constructor(private inj: Injector) {}

            intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
                const auth = this.inj.get(AuthService);
                const token = this.auth.getToken();
                return next.handle(req);
            }
        }

don't forget import

import {Injector} from '@angular/core';
Shailesh Ladumor
  • 7,052
  • 5
  • 42
  • 55
2

I got a similar issue with the same design of an auth service coupled with an interceptor.

@Injectable() AuthInterceptorService {
    constructor (authApi: AuthApiService) {}
    handle () {...do the job}
}

@Injectable() AuthApiService {
   constructor () {
       // ...do some setup with a request, such as to get current session
       // that leads to an indirect circular between 2 constructors. 
   }

}

In my case, i found the cause is that I try to start a http request in the auth service's constructor. At that point the injector seems haven't completed the registration of the auth service's instance, while the http client captures the new request and tried to instantiaze the interceptor again, since the previous interceptor instance was stuck in its constructor on the call stack too!

That recursive invoke with two constructors, breaks the singleton pattern of the injector and leads to out of call stack.

Edward Liang
  • 100
  • 1
  • 8
  • How did you resolve the problem you're describing? I just ran into the exact same issue. – poprogrammer Jul 10 '18 at 21:34
  • 2
    Just don't request http resources in the constructor of the interceptors as well as its dependencies. You can defer that to webhooks. – Edward Liang Jul 15 '18 at 07:13
  • 2
    Thanks for replying Edward. Webhooks aren't something that would solve my problem. My class (service actually) requires information from an http request to be initialized. I ended up moving my code out of the constructor to a separate method. Then I call that method from an APP_INITIALIZER to ensure the service is fully initialized for the rest of the application to use. This works for me, but might not be the best case for everyone as it makes the http request run synchronously before the application starts. Thank you for posting this answer as it helped me figure out this problem in my app. – poprogrammer Jul 16 '18 at 13:30
  • @poprogrammer i checked out docs about APP_INITIALIZER and I believe you got a designed way for initialization code. Thx for updates. – Edward Liang Jul 26 '18 at 08:29
2

For me @Benjineer answar helped to provide as a dependency in app.module.ts the required sercvice.

Although what is interensting, in the order you add as a dependency in app.module.ts you get it in the constructor of the service.

For Example: in the app module you provide in the order of AuthService, ErroMsgService

deps: [AuthService, ErrorMsgService]

in the HttpInterceptor constructor you have to create them in the same order

 constructor(
    private authService: AuthService,
    private errorService: ErrorMsgService
    ) {}
Gabor
  • 352
  • 5
  • 14
1

For this issue, make sure that the service which you are injecting in Http interceptor needs to be added in providers along with the HTTP_INTERCEPTORS in the module.

providers: [ AuthService,
    {
      provide: HTTP_INTERCEPTORS,
      useClass: HttpConfigInterceptor,
      multi: true
    }
]
0

If in case, service class is declared in the same file of Injector class then service class should be declared and defined first. Followed by the Injector class where the service class injected as a dependency.

Following above structure does resolves the problem in my case.

Sasi Kumar M
  • 2,440
  • 1
  • 23
  • 23