How to inject HttpClient in static method or custom class?
Asked Answered
I

4

26

I'd like to use angular HttpClient in static method or class (in class it can't be defined as constructor parameter).

I tried something like:

export class SomeNotInjectableService {
  static doSomething() {
    const injector = Injector.create({
      providers: [{provide: HttpClient, deps:[]}]
    });
    const httpClient: HttpClient = injector.get(HttpClient);

    httpClient.request(...); // error Cannot read property 'handle' of undefined
  }
}

this is a try of injecting manually the client in static service method. Doesn't work. I'm curious how to do this or how to inject the client in normal method but in a class which isn't a component.

Infracostal answered 27/3, 2018 at 8:29 Comment(4)
You could always avoid static methods...Feudatory
Still, the question was how to inject HttpClient inside normal method but using injector instead of constructor. Would above solution work?Infracostal
You also need to inject the Injector in the static method :)Feudatory
I'm not able to find solution. Help me out plz..Oligocene
D
28

I'm not exactly sure why it does not work the way you tried (probably something missing when you create the injector), but it works if you use an 'injected' injector

If you look at the source code throwing the error you see that it mentions handlers for the request, which seems to be null in your example. Maybe angular registers some internal handlers when the HttpClient is provided the 'traditional' way, but not the way you do it

// Start with an Observable.of() the initial request, and run the handler (which
// includes all interceptors) inside a concatMap(). This way, the handler runs
// inside an Observable chain, which causes interceptors to be re-run on every
// subscription (this also makes retries re-run the handler, including interceptors).

var /** @type {?} */ events$ = rxjs_operator_concatMap.concatMap.call(rxjs_observable_of.of(req), function (req) { return _this.handler.handle(req); });

Work around:

app.module.ts

import {Injector} from '@angular/core';

export let InjectorInstance: Injector;

export class AppModule 
{
  constructor(private injector: Injector) 
  {
    InjectorInstance = this.injector;
  }
}

Your static class/method

import {InjectorInstance} from './app.module';

export class SomeNotInjectableService {
  static doSomething() 
  {
  /*  const injector = Injector.create({
      providers: [{provide: HttpClient, deps:[]}]
    });
    const httpClient: HttpClient = injector.get(HttpClient);
*/
    const httpClient =  InjectorInstance.get<HttpClient>(HttpClient);

    httpClient.request(...)...
  }
}

Example on Stackblitz: https://stackblitz.com/edit/angular-li8b37?file=app%2Fapp.component.ts

Defrayal answered 27/3, 2018 at 8:50 Comment(3)
Is it the only solution? What if I'd like to write it as independent library and avoid declaring anything in any component/service? That's only an example. Your solution is more than satisfying to me but maybe there is another option.Infracostal
@Infracostal Sorry, I'm not really sure. Maybe pass the httpClient instance to your static method as a workaround?Defrayal
This is for httpclient. What if we have quite a few number of injectable services to include in this custom class.Grass
O
35

You can also skip the injector if you don't have one. That means doing the 'injecting' yourself. I don't recommend doing this. If you really want to use a static method (in favor of a proper service), pass all the needed stuff.

I'm not sure if this isn't already obvious but any HTTP interceptor will be missing from this httpClient pipeline, since there's no way of resolving them.

import { HttpClient, HttpXhrBackend } from '@angular/common/http';

const httpClient = new HttpClient(new HttpXhrBackend({ build: () => new XMLHttpRequest() }));
httpClient.get('test').subscribe(r => console.log(r));

or using your own created injector (if you don't like passing ctor args):

const injector = Injector.create({
    providers: [
        { provide: HttpClient, deps: [HttpHandler] },
        { provide: HttpHandler, useValue: new HttpXhrBackend({ build: () => new XMLHttpRequest }) },
    ],
});
const httpClient: HttpClient = injector.get(HttpClient);
httpClient.get('test').subscribe(r => console.log(r));
Odey answered 27/3, 2018 at 10:17 Comment(0)
D
28

I'm not exactly sure why it does not work the way you tried (probably something missing when you create the injector), but it works if you use an 'injected' injector

If you look at the source code throwing the error you see that it mentions handlers for the request, which seems to be null in your example. Maybe angular registers some internal handlers when the HttpClient is provided the 'traditional' way, but not the way you do it

// Start with an Observable.of() the initial request, and run the handler (which
// includes all interceptors) inside a concatMap(). This way, the handler runs
// inside an Observable chain, which causes interceptors to be re-run on every
// subscription (this also makes retries re-run the handler, including interceptors).

var /** @type {?} */ events$ = rxjs_operator_concatMap.concatMap.call(rxjs_observable_of.of(req), function (req) { return _this.handler.handle(req); });

Work around:

app.module.ts

import {Injector} from '@angular/core';

export let InjectorInstance: Injector;

export class AppModule 
{
  constructor(private injector: Injector) 
  {
    InjectorInstance = this.injector;
  }
}

Your static class/method

import {InjectorInstance} from './app.module';

export class SomeNotInjectableService {
  static doSomething() 
  {
  /*  const injector = Injector.create({
      providers: [{provide: HttpClient, deps:[]}]
    });
    const httpClient: HttpClient = injector.get(HttpClient);
*/
    const httpClient =  InjectorInstance.get<HttpClient>(HttpClient);

    httpClient.request(...)...
  }
}

Example on Stackblitz: https://stackblitz.com/edit/angular-li8b37?file=app%2Fapp.component.ts

Defrayal answered 27/3, 2018 at 8:50 Comment(3)
Is it the only solution? What if I'd like to write it as independent library and avoid declaring anything in any component/service? That's only an example. Your solution is more than satisfying to me but maybe there is another option.Infracostal
@Infracostal Sorry, I'm not really sure. Maybe pass the httpClient instance to your static method as a workaround?Defrayal
This is for httpclient. What if we have quite a few number of injectable services to include in this custom class.Grass
E
1

base on Andrew answer. If you want to use interceptors in this this httpClient pipeline, add two redefined classes from angular repo http/src/interceptor.ts and http/src/module.ts:

class HttpInterceptorHandler implements HttpHandler {
  constructor(private next: HttpHandler, private interceptor: HttpInterceptor) {}

  handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
      return this.interceptor.intercept(req, this.next);
  }
}
class HttpInterceptingHandler implements HttpHandler {
  private chain: HttpHandler|null = null;
  private httpBackend:HttpHandler;
  constructor(private injector: Injector) {
      this.httpBackend = new HttpXhrBackend({ build: () => new XMLHttpRequest });
  }

  handle(req: HttpRequest<any>): Observable<HttpEvent<any>> {
      if (this.chain === null) {
          const interceptors = this.injector.get(HTTP_INTERCEPTORS, []);
          this.chain = interceptors.reduceRight((next, interceptor) => new HttpInterceptorHandler(next,interceptor),this.httpBackend);
      }
      return this.chain.handle(req);
    }
}

Interceptors need without @Injectable decorator:

class HttpIntersept implements HttpInterceptor{
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
      console.log(req.urlWithParams);
      return next.handle(req)
  }
}

And like Andrew was say

const injector = Injector.create({
providers: [
    { provide: HTTP_INTERCEPTORS, useClass: HttpIntersept, multi: true, deps: []},
    { provide: HTTP_INTERCEPTORS, useClass: HttpIntersept2, multi: true, deps: []},
    { provide: HttpHandler, useClass:HttpInterceptingHandler,deps [Injector,HTTP_INTERCEPTORS]},
    { provide: HttpClient, deps: [HttpHandler] }
 ],
});
Ezarras answered 8/8, 2018 at 14:56 Comment(0)
W
-1

Passing the need service/object as a parameter helps a lot. In addition, it helps testing and code "readability". This following solution works with any type of object you are trying to inject. And, at least, you inject it where/when needed. The calling object is responsible for injecting the needed object.

export class SomeNotInjectableService {
  static doSomething(injected: any) {
    httpClient = injected as HttpClient;
    if(httpClient) {
       httpClient.get(...);
     }
  }
}

then in your calling component or service, use it like this

  ...
  export class MyService/*or MyComponent*/{
      constructor(private http: HttpClient){}
      doTheThing(){
          SomeNotInjectableService.doSomething(this.http)/*...subscribe()*/;
      }
  }
Witting answered 25/3, 2020 at 13:19 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.