Getting instance of service without constructor injection

AngularAngular2 DiAngular2 Injection

Angular Problem Overview


I have a @Injectable service defined in bootstrap. I want to get the instance of the service without using constructor injection. I tried using ReflectiveInjector.resolveAndCreate but that seem to create a new instance.

The reason I'm trying to do is I have a base component derived by many components. Now I need to access a service but I don't want to add it to the ctor because I don't want to inject the service on all of the derivative components.

TLDR: I need a ServiceLocator.GetInstance<T>()

UPDATE: Updated code for RC5+: https://stackoverflow.com/questions/39409328/storing-injector-instance-for-use-in-components

Angular Solutions


Solution 1 - Angular

In the updated Angular where ngModules are used, you can create a variable available anywhere in the code:

Add this code in app.module.ts

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

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

Now, you can use the AppInjector to find any service in anywhere of your code.

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

const myService = AppInjector.get(MyService);

Solution 2 - Angular

Yes, ReflectiveInjector.resolveAndCreate() creates a new and unconnected injector instance.

You can inject Angulars Injector instance and get the desired instance from it using

constructor(private injector:Injector) {
  injector.get(MyService);
}

You also can store the Injector in some global variable and than use this injector instance to acquire provided instances for example like explained in https://github.com/angular/angular/issues/4112#issuecomment-153811572

Solution 3 - Angular

Another approach would consist of defining a custom decorator (a CustomInjectable to set the metadata for dependency injection:

export function CustomComponent(annotation: any) {
  return function (target: Function) {

    // DI configuration
    var parentTarget = Object.getPrototypeOf(target.prototype).constructor;
    var parentAnnotations = Reflect.getMetadata('design:paramtypes', parentTarget);

    Reflect.defineMetadata('design:paramtypes', parentAnnotations, target);

    // Component annotations / metadata
    var annotations = Reflect.getOwnMetadata('annotations', target);
    annotations = annotations || [];
    annotations.push(annotation);
    Reflect.defineMetadata('annotations', annotations, target);
  }
}

It will leverage the metadata from the parent constructor instead of its own ones. You can use it on the child class:

@Injectable()
export class SomeService {
  constructor(protected http:Http) {
  }
}

@Component()
export class BaseComponent {
  constructor(private service:SomeService) {
  }
}

@CustomComponent({
  (...)
})
export class TestComponent extends BaseComponent {
  constructor() {
    super(arguments);
  }

  test() {
    console.log('http = '+this.http);
  }
}

See this question for more details:

Solution 4 - Angular

After running into this issue a few times, I've devised a good way to overcome it by using a getter with the Angular Injector service, instead directly injecting the service in the constructor. This allows the service time to be constructed before being referenced. My example uses only services but the same thing can be applied to a component using a service, just put the getter in a component instead BService in the example.

What I did was use a getter to inject the service into a class property using the Injector class, if the class property was not already set before, so the service is only ever injected once (the first time the getter is called). This allows the service to be used in basically the same way as if it was injected in the constructor but without a circular reference error. Just use the getter this.aService. They only time this won't work is if you are trying to use AService within the constructor of Bservice, then you would have the same issue of a circular reference since Aservice would not be ready yet. By using the getter you are deferring injecting the service until you need it.

There are arguments that, AService depending on BService, and BService depending on AService, is bad form but there exceptions to every rule and every situation is different so this is an easy and effective way to deal with this issue in my opinion.

// a.service.ts
import { Injectable } from '@angular/core';

import { BService } from './b.service';

@Injectable({
  providedIn: 'root'
})
export class AService {

  constructor(
    private bService: BService,
  ) { }

  public foo() {
    console.log('foo function in AService!');
    this.bService.bar();
  }
}
// b.service.ts
import { Injectable, Injector } from '@angular/core';

import { AService } from './a.service';


@Injectable({
  providedIn: 'root'
})
export class BService {
  // Use the getter 'aService' to use 'AService', not this variable.
  private _aService: AService;

  constructor(
    private _injector: Injector,
  ) { }

  // Use this getter to use 'AService' NOT the _aService variable.
  get aService(): AService {
    if (!this._aService) {
      this._aService = this._injector.get(AService);
    }
    return this._aService;
  }

  public bar() {
    console.log('bar function in BService!');
    this.aService.foo();
  }
}

Solution 5 - Angular

StoreService .ts

  import { Injectable} from '@angular/core';
    
    @Injectable()
    export class StoreService {
      static isCreating: boolean = false;
      static instance: StoreService ;
    
      static getInstance() {
        if (StoreService.instance == null) {
          StoreService.isCreating = true;
          StoreService.instance = new StoreService ();
          StoreService.isCreating = false;
        }
        return StoreService.instance;
      }
      constructor() {
        if (!StoreService.isCreating) {
          throw new Error('You can\'t call new in Singleton instances! Call StoreService.getInstance() instead.');
        }
     }
    
  MyAlertMethod(){
    alert('hi);
    }
  }

.ts

//call this service directly in .ts as below:-

 StoreService.getInstance().MyAlertMethod();

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiondstrView Question on Stackoverflow
Solution 1 - AngularElias RezerView Answer on Stackoverflow
Solution 2 - AngularGünter ZöchbauerView Answer on Stackoverflow
Solution 3 - AngularThierry TemplierView Answer on Stackoverflow
Solution 4 - AngularRcoderNYView Answer on Stackoverflow
Solution 5 - AngularMahiView Answer on Stackoverflow