Angular 2 Inject Dependency outside Constructor
Asked Answered
A

2

18

I am currently digging into DI in Angular 2. I'm implementing a REST-Client using a generic subtypes for concrete Datatypes like this:

class RESTClient<T>{
    constructor() {
        var inj =  ReflectiveInjector.resolveAndCreate([HTTP_PROVIDERS]);
        this.http = inj.get(Http);
        this.conf = RESTConfiguration;
    }
}
class BookClient extends RESTClient<Book>{      
    constructor(){
        // since I dont want to inject the HTTP Providers here, I'm using a custom    injector in the super class
        super();
    }
}

class WriterClient extends RESTClient<Writer>{      
    ...
    ...
}

So as I understand, there will be one http service shared between all RESTClients injected by the superclasses REST-Service.

Now I want to have a RESTConfiguration class as such:

@Injectable()
export class RESTConfiguration {
    get baseURL() {
     return this._baseURL;
    }

    set baseURL(value) {
        alert("sets value to"+value);
        this._baseURL = value;
    }

    private _baseURL;

}

It should be configured in the main app as such:

initializeApp(){
  this.restconf.baseURL = "http://localhost:3004/";
}
bootstrap(MyApp, [RESTConfiguration]).then();

I'm now wondering how to inject one singleton instance of my RESTConfiguration into the RESTService class without passing it to the constructor which I want to remain argument-less in order to reduce code duplication and avoid issues with generics in typescript.

In the above example (first code snippet) I'm trying to inject my configuration using the ReflectiveInjector I created which delivers me a custom instance of my Configuration.

I thought about several solutions:

  1. Getting access to the Apps "global injector" by making one available using a service or some static class property

  2. Implementing extra singleton-logic into my configuration

  3. finding a way to use the angular-native injection method outside of the constructor?

Are there mistakes in my thinking or am I misusing the DI framework ?

Apennines answered 23/8, 2016 at 12:56 Comment(5)
Looks like a dup of #39039291Chloric
After reading the linked question above I feel the author very much - I'm tending to use his "dirty hack" for the classes to pass the global injector - I definitely don't want to duplicate code in ~20-30 classes. Any way to get around this? What about way with the custom injector as shown above? I could then directly attach the configuration to my Service or make it just a simple static class...Apennines
This creates an independent injector. Providers registered in any module and the instances created for these providers won't be returned by this custom injector. You can do as in the answer of the linked question - to pass the injector of your Angular2 app around and acquiring services from this injector instead.Chloric
Yes this is working ! So how bad is this then? (if I got you right you called this a "dirty hack" in the question above). I assume this limits testability and reusability of the subclasses?Apennines
It hides dependencies which makes code harder to read. It violates expectations of one familiar with how Angular2 DI works. It breaks offline compilation that generates static code to replace dynamic DI to improve performance and reduce code size.Chloric
R
37

This should provide a solution for this issue but also help in any case where one needs to inject a service without supplying it as a constructor parameter.

I saw this answer in another post: Storing injector instance for use in components

You can configure the Angular Injector in your AppModule class, and then use it in any other class (you can access AppModule's members from any class).

In AppModule add:

export class AppModule { 
  /**
     * Allows for retrieving singletons using `AppModule.injector.get(MyService)`
     * This is good to prevent injecting the service as constructor parameter.
     */
    static injector: Injector;
    constructor(injector: Injector) {
        AppModule.injector = injector;
    }
}

Then in your other class you can do the following (for this question replace MyService with Http):

@Injectable()
export class MyClass{
    private myService;

    constructor(){
        this.myService = AppModule.injector.get(MyService);
    }
}

This would be equivalent to using:

constructor(private myService: MyService){}
Radiator answered 30/5, 2017 at 11:2 Comment(5)
It's works!. In my case: Get service from another Module (not lazy) and all resolve the instance! Thanks man.Trover
To add to what @Radiator is suggesting, you can add { provide: Injector, useValue: AppModule.injector } to the providers array. Once you are finished with that you can inject the Injector class directly into any class constructor with the Injector class reference. constructor(injector: Injector) {}Electrician
I'm getting circular dependencies when referencing AppModule in a superclass that a bunch of child classes extend from.Primacy
In my case AppModule.injector is undefined. I get: Cannot read property 'get' of undefinedCoercion
Use angular 9, and this resipe doesn't work. I am getting Property 'injector' does not exist on type 'typeof AppModule'.ts(2339) error.Fluctuation
S
4

Since Angular 14.2 you can use inject() from "@angular/core" in an injection context.

From in v14.2 / v15 you can use EnvironmentInjector.runInContext() to run any code in an injection context. Since v16, you can use runInInjectionContext() with any type of injector (Injector, EnvironmentInjector() ...)

Way better than the suggested answer.

Sample:

import { EnvironmentInjector, inject, runInInjectionContext } from "@angular/core";

class MyService { }

class Foo {
  constructor(private environment: EnvironmentInjector) {
    runInInjectionContext(environment, () => {
      const myService = inject(MyService)
    })
  }
}
Sunderance answered 4/11, 2022 at 17:5 Comment(3)
EnvironmentInjector isn't even needed in some casesCerise
Can we use injected service in other components constructor after inject(...) or the context is scoped to the Foo EnvironmentInjector ? For example, in your example, any way to do something like that after Foo has been loaded : jsfiddle.net/4rxo6ugf/1Doralia
You need to be in an injection context angular.io/guide/dependency-injection-contextSunderance

© 2022 - 2024 — McMap. All rights reserved.