Getting instance of service without constructor injection
Solution 1
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 2
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 3
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
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();
}
}
Related videos on Youtube
themiurge
Updated on July 08, 2022Comments
-
themiurge almost 2 years
I have a
@Injectable
service defined in bootstrap. I want to get the instance of the service without using constructor injection. I tried usingReflectiveInjector.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+: Storing injector instance for use in components
-
themiurge about 8 yearsStoring injector in a global variable in bootstrap.then() sound like what I need.
-
parliament over 7 yearsThe problem is tsc doesn't let me call super(arguments) because the arguments for the base constructor don't match.
-
Jon Miles over 7 yearsThe question says without a constructor which makes this answer rather unhelpful.
-
Günter Zöchbauer over 7 yearsWithout constructor there is no injection. Should we delete the question then ;-)
-
Max Koretskyi over 7 years@GünterZöchbauer, do you know which injector instance is injected when used like this
private injector:Injector
? Is it root injector instance? Or it depends? -
Günter Zöchbauer over 7 yearsIf the constructor is in a component, it's the components injector, in a service it's the module injector (basically root injector if it's not a lazy loaded module). You can also get the parent components injector by adding
@SkipSelf()
. You can also interate theparent
getter, to get the root injector. -
Max Koretskyi over 7 years@GünterZöchbauer, got it, thanks! it's the components injector - even if it's not defined any providers?
-
Günter Zöchbauer over 7 yearsYes, a component always has some providers, like
ElementRef
registered. There is no component or directive without an injector. An injector always provides its own providers and all providers of parent injectors - just as a note. -
Max Koretskyi over 7 yearsI've never seen mentioning that a directive has it's own injector. Do you know where can I read about that? And is my understanding correct that
private injector:Injector
is an instance returned byReflectiveInjector.resolveAndCreate
? -
Günter Zöchbauer over 7 yearsI don't know about docs, but you can register providers on a directive the same as a component. A component is basically just a directive with a view. If a directive is added to a component they share providers (don't know how this is exactly implemented). Yes, I'm pretty sure a components injector is built this way.
-
Royi Namir about 7 years@GünterZöchbauer It didn;t work for me with
opaqueTokens
. I've Already asked here . I'm hoping to find a solution -
Arjan about 7 yearsNice. Might be a good answer at stackoverflow.com/questions/39409328/… as well.
-
slowkot almost 7 yearsin that particular case we can just remove constructor from derived class github.com/Microsoft/TypeScript/issues/12439
-
Davide Perozzi over 6 yearsYou're the king!
-
Rhyous almost 6 yearsThis just puts something else in the constructor. How do you get it without a constructor.
-
Günter Zöchbauer almost 6 years@Rhyous magic? You can share the injector in a static field or global variable and access it from there, but first you need to inject it "somewhere" using a constructor and assign it to the static field.
-
Rhyous almost 6 yearsIs the DI container ng uses that bad, that it doesn't even support the basic features of a common DI container?
-
Rhyous almost 6 years@GünterZöchbauer That isn't an answer, that is an excuse.
-
Günter Zöchbauer almost 6 yearsWhy would I want to spend my time for someone who downvotes my answer just becuase he wants something else?
-
aruno about 5 yearsWow are we allowed to do this ?
-
Vahid over 4 yearsThis has a small problem. You have to make separate injectors for lazy loaded modules that provide a service.
-
Emeke Ajeh almost 4 yearsHow can this work with services that requires other services injected in the constructor?
-
Bernoulli IT over 3 yearsYeah 🥳 lets make everything just a global public static monolithic variable. NOT 🤨
-
Nanda Kishore Allu about 3 yearsHi, injector.get(ViewContainerRef) is returning null in my case. Could you please reply me with the possible situations if any to return null.