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.

dstr
  • 8,362
  • 12
  • 66
  • 106

4 Answers4

74

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);
Arjan
  • 22,808
  • 11
  • 61
  • 71
  • 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? – angularrocks.com May 22 '17 at 03:23
  • 2
    @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. – Arjan May 22 '17 at 06:15
  • 2
    This is just great. Solved all of my problems – Vahid Feb 12 '18 at 12:54
  • @Arjan Hi, I'm getting: "Uncaught TypeError: Cannot read property 'get' of undefined", and I did everything as you specified – user3362334 Dec 07 '20 at 15:43
  • 1
    @Arjan Even I got service as undefined after this. – Ankit Tanna Jul 17 '21 at 14:37
  • 1
    How can I initialize the AppInjector in a Unit Test? – Lê Quang Bảo Feb 17 '22 at 03:00
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);
    }
}
dstr
  • 8,362
  • 12
  • 66
  • 106
  • 1
    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? – Petr Marek Nov 14 '16 at 12:11
  • @PetrMarek: I don't remember it quite well but I think you only get ApplicationRef in ngDoBootstrap event. – dstr Nov 15 '16 at 07:23
  • 4
    I'm storing Injector's reference in AppModule constructor and it seems to work fine. (don't using ngDoBootstrap) – Petr Marek Nov 15 '16 at 10:06
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);
});
jarjar
  • 359
  • 4
  • 10
  • Are you sure this works? Because the components execute during bootstrap and injector works after components, leaving me with null ServiceLocator.instance? – dstr Sep 28 '16 at 11:37
  • @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 !) – jarjar Sep 28 '16 at 15:16
  • Can you explain how does it work? Can I in this step read config object from Window and inject to Angular? –  Mar 03 '21 at 22:45
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(...
  }

}
Felix
  • 1,662
  • 2
  • 18
  • 37