Storing injector instance for use in components
Asked Answered
O

4

47

Before RC5 I was using appref injector as a service locator like this:

Startup.ts

bootstrap(...)
.then((appRef: any) => {
    ServiceLocator.injector = appRef.injector;
});

ServiceLocator.ts

export class ServiceLocator {
    static injector: Injector;
}

components:

let myServiceInstance = <MyService>ServiceLocator.injector.get(MyService)

Now doing the same in bootstrapModule().then() doesn't work because components seems to start to execute before the promise.

Is there a way to store the injector instance before components load?

I don't want to use constructor injection because I'm using the injector in a base component which derived by many components and I rather not inject the injector to all of them.

Original answered 9/9, 2016 at 10:21 Comment(0)
O
12

I've managed to do it using manual boostrapping. Don't use "bootstrap: [AppComponent]" declaration in @NgModule, use ngDoBootstrap method instead:

export class AppModule {
    constructor(private injector: Injector) {
    }

    ngDoBootstrap(applicationRef: ApplicationRef) {
        ServiceLocator.injector = this.injector;
        applicationRef.bootstrap(AppComponent);
    }
}
Original answered 11/9, 2016 at 11:11 Comment(3)
why not to do the assigment in AppModule's constructor leaving bootstrap: [AppComponent] and not using ngDoBootstrap? Or is there a chance that AppModule's constructor could be called after bootstraping?Piggyback
@PetrMarek: I don't remember it quite well but I think you only get ApplicationRef in ngDoBootstrap event.Original
I'm storing Injector's reference in AppModule constructor and it seems to work fine. (don't using ngDoBootstrap)Piggyback
B
75

For today's TypeScript and Angular 5, avoiding WARNING in Circular dependency detected when importing the global injector, first declare a helper, e.g. app-injector.ts:

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

/**
 * Allows for retrieving singletons using `AppInjector.get(MyService)` (whereas
 * `ReflectiveInjector.resolveAndCreate(MyService)` would create a new instance
 * of the service).
 */
export let AppInjector: Injector;

/**
 * Helper to set the exported {@link AppInjector}, needed as ES6 modules export
 * immutable bindings (see http://2ality.com/2015/07/es6-module-exports.html) for 
 * which trying to make changes after using `import {AppInjector}` would throw:
 * "TS2539: Cannot assign to 'AppInjector' because it is not a variable".
 */
export function setAppInjector(injector: Injector) {
    if (AppInjector) {
        // Should not happen
        console.error('Programming error: AppInjector was already set');
    }
    else {
        AppInjector = injector;
    }
}

Next, in your AppModule, set it using:

import {Injector} from '@angular/core';
import {setAppInjector} from './app-injector';

export class AppModule {
    constructor(injector: Injector) {
        setAppInjector(injector);
    }
}

And wherever needed, use:

import {AppInjector} from './app-injector';
const myService = AppInjector.get(MyService);
Brig answered 29/4, 2017 at 12:38 Comment(6)
Had this one initially var injector = ReflectiveInjector.resolveAndCreate([AppService); var appAlertService = injector.get(AppService); that didn't work as my AppService had ngrx Store injected and throw no Store provider found, so with AppInjector.get(AppService); all working just fine. Any ideas why?Assistant
@Kuncevic, I don't know. But just to be sure: your original version is creating a new instance of AppService, not shared with other components that might also use it (whereas the solution above gets one a shared singleton). I'd still expect your newly created instance to be correctly injected with any shared dependencies it needs, but apparently not.Brig
This is just great. Solved all of my problemsDrawl
@Brig Hi, I'm getting: "Uncaught TypeError: Cannot read property 'get' of undefined", and I did everything as you specifiedTreatise
@Brig Even I got service as undefined after this.Aspergillus
How can I initialize the AppInjector in a Unit Test?Depersonalization
O
12

I've managed to do it using manual boostrapping. Don't use "bootstrap: [AppComponent]" declaration in @NgModule, use ngDoBootstrap method instead:

export class AppModule {
    constructor(private injector: Injector) {
    }

    ngDoBootstrap(applicationRef: ApplicationRef) {
        ServiceLocator.injector = this.injector;
        applicationRef.bootstrap(AppComponent);
    }
}
Original answered 11/9, 2016 at 11:11 Comment(3)
why not to do the assigment in AppModule's constructor leaving bootstrap: [AppComponent] and not using ngDoBootstrap? Or is there a chance that AppModule's constructor could be called after bootstraping?Piggyback
@PetrMarek: I don't remember it quite well but I think you only get ApplicationRef in ngDoBootstrap event.Original
I'm storing Injector's reference in AppModule constructor and it seems to work fine. (don't using ngDoBootstrap)Piggyback
B
3

Another solution with angular 2.0.0 final :

platformBrowserDynamic().bootstrapModule(AppModule, [
  {
    defaultEncapsulation: ViewEncapsulation.Emulated,
    providers: [
      { provide: TRANSLATIONS, useValue: TRANSLATION },
      { provide: TRANSLATIONS_FORMAT, useValue: 'xlf' },
      { provide: LOCALE_ID, useValue: 'fr' }
    ]
  }
]).then((modref: NgModuleRef<any>) => {
  appInjector(modref.injector);
});
Boorer answered 28/9, 2016 at 10:16 Comment(3)
Are you sure this works? Because the components execute during bootstrap and injector works after components, leaving me with null ServiceLocator.instance?Original
@dstr, I'm using this code on my application and it works. Which angular version are you using ? Can you past a copy of the error stack trace ? (Sorry for my bad english !)Boorer
Can you explain how does it work? Can I in this step read config object from Window and inject to Angular?Dorothi
P
1

Class based solution.

Often I need to refer to a service from classes that are used by other classes. Injecting services via a constructor is cumbersome and causes issues for the calling classes, which do not need the given service(s).

In Angular 8, I setup a library class: ServiceInjectorModule (can also be used inside sub-modules)

(derived from similar answers on stackoverflow)

File: service-injector.module.ts

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

export let ServiceInjector: Injector;

@NgModule()
export class ServiceInjectorModule {
  constructor(private injector: Injector) {
    ServiceInjector = this.injector;
  }
}

File: my-lib.module.ts

Note: You can skip this library module as it is only more convenient if you have other services or modules to use. If you skip it, import ServiceInjectorModule directly to your AppModule.

import { ServiceInjectorModule } from './service-injector.module';

const impExpModules = [
  ...,
  ServiceInjectorModule,
  ...
];


@NgModule({
  imports: [impExpModules],
  exports: [impExpModules]
  })
export class MyLibModule {}

Import MyLibModule to your AppModule or where best fits.

Now in your components or classes, simply:

import { ServiceInjector } from '../modules/lib/service-injector.module';
import { MyService } from '../services/my.service';


export class MyCurrentClass {

  myService = ServiceInjector.get(MyService);

  ...
  
  myLocalFunction() {
      ...
      this.myService.myServiceFunction(...
  }

}
Pugh answered 27/8, 2020 at 1:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.