StaticInjectorError[HttpClent]: Function/class not supported
Asked Answered
S

1

2

I am trying to inject the HttpClientModule manually which runs outside independent(may be!) from app. Before static injector i was using reflective injector and code was working fine but now that reflective injector has been deprecated and I want to update my code with static injector.

//appInjector.ts
export class AppInjector {

  private static _instance: AppInjector = new AppInjector();
  private _injector;

  constructor() {
    console.log('app-injector');
    AppInjector._instance = this;
    this._injector = ReflectiveInjector.resolveAndCreate([
        ...[getAnnotations(HttpClientModule)[0].providers],
        MY_HTTP_DEPENDENT_PROVIDERS
        ]);
        
  static getInstance(): AppInjector {
    return AppInjector._instance;
  }

  get(cls: any): any {
    return this._injector.get(cls);
  }
}
//someFile.ts
const translate = AppInjector.getInstance().get(TranslateResource);

refer This Post for annotation fn. Now when i am trying to use Http client with static inject it gives error: StaticInjectorError[HttpClent]: Function/class not supported

//app module
@NgModule({
  imports: [],
  declarations: [],
  providers: [],
  entryComponents: [App]
})
export class AppModule {
  ngDoBootstrap(app) {
    console.log('bootstrapping');
    app.bootstrap(App);
  }

so if i log it will log app-injector and then bootstrapping.

Spense answered 1/5, 2018 at 14:54 Comment(0)
P
1

StaticInjector is supposed to be a replacement for ReflectiveInjector that doesn't require Reflect API. getAnnotations is low-level hack and it likely won't work with StaticInjector in its current state. Also, getAnnotations is incompatible with AOT by design.

It's preferable to create an injector for a module the way it is supposed to be done by the framework, i.e. a module should be bootstrapped. Since there is no component to bootstrap, ngDoBootstrap hook should be specified.

By default, bootstrap process is asynchronous. If this is not a problem, initialization promise can be chained to get module instance.

An example:

@NgModule({
  imports: [BrowserModule, HttpClientModule]
})
export class MyHttpModule {
  static httpClient?: HttpClient;
  httpClient?: HttpClient;

  constructor(private _injector: Injector) {}

  ngDoBootstrap() {
    MyHttpModule.httpClient = this.httpClient = this._injector.get(HttpClient);
  }  
}

platformBrowserDynamic().bootstrapModule(MyHttpModule)
.then((myHttpModule: NgModuleRef<MyHttpModule>) => {
    // HttpClient instance is available here
    const httpClient = myHttpModule.instance.httpClient;
    httpClient.get('/foo', { responseType: 'text'}).subscribe();
})
.catch(err => console.error(err));

This approach is compatible with both JIT and AOT (which is good for using HttpClient apart from Angular because this lowers the footprint significantly) out of the box.

Otherwise custom synchronous bootstrap routine can be performed instead. This is possible because HttpClient doesn't require asynchronous initialization.

An example:

@NgModule({
  imports: [BrowserModule, HttpClientModule]
})
export class MyHttpModule {
  static httpClient?: HttpClient;

  constructor(public _injector: Injector) {
    MyHttpModule.httpClient = this._injector.get(HttpClient);
  }

  ngDoBootstrap() {}  
}

const platform = platformBrowserDynamic();
const compiler = platform.injector.get(CompilerFactory).createCompiler();
const moduleFactory = compiler.compileModuleSync(MyHttpModule);

platform.bootstrapModuleFactory(moduleFactory)
.catch(err => console.error(err));

const httpClient = MyHttpModule.httpClient;
httpClient.get('/foo').subscribe();

This will work in JIT, but AOT can't be efficiently handled by Angular CLI in the code above. The code involves the compiler, which isn't needed in AOT compilation mode (that's its purpose). In order to use AOT, it should be compiled with ngc compiler and a separate entry point that uses module factories should be created. Bootstrap routine becomes even simpler, because it doesn't involve compiler, something like:

...
import { platformBrowser } from '@angular/platform-browser-dynamic';
import { AppModuleNgFactory } from '<path to aot>/src/app/my-http-module.ngfactory';

const platform = platformBrowser();
platform.bootstrapModuleFactory(AppModuleNgFactory)
.catch(err => console.error(err));

const httpClient = MyHttpModule.httpClient;
httpClient.get('/foo').subscribe();
Pointless answered 1/5, 2018 at 20:42 Comment(3)
thank you for the detailed answer but may be i got my question wrong if i do what you said where would my AppModule go and also the way i am doing i can access all the resources that i provide in relective injector array anywhere in the app and also AppInjector gets triggered before even app bootstraps.Spense
I updated the question. Please check and let me know if you need more details and also i am running old version of webpack not angular cli yet. Trying to upgrade the app one thing at a time.Spense
It can go wherever you like, you just need to be sure you've bootstrapped it when you need to access HttpClient. In the code listed above the injector is instance property, _injector, you could use it as well if you need. This code presumes that there is no Angular app, only HttpClient that is used with non-Angular app. If this isn't so, this could be XY problem - you could share HttpClient and injector from Angular app the same way.Pointless

© 2022 - 2024 — McMap. All rights reserved.