32

I have angular class that represents shape. I want to be able to instantiate multiple instances of that class using constructor.

The constructor takes multiple arguments representing properties of that shape.

constructor(public center: Point, public radius: number, fillColor: string,
    fillOpacity: number, strokeColor: string, strokeOpacity: number, zIndex: number)

Inside my class I want to use service that provides capability to draw shapes on the map. Is it possible to inject that service into my class and still use constructor the standard way.

So I want to do something like below and have Angular automatically resolve injected dependency.

constructor(public center: GeoPoint, public radius: number, 
    fillColor: string, fillOpacity: number, strokeColor: string, strokeOpacity: number, 
    zIndex: number, @Inject(DrawingService) drawingService: DrawingService)
Liplattaa
  • 1,231
  • 2
  • 11
  • 10

5 Answers5

46

I've managed to resolve my problem.

Angular 2 - 4 provides reflective injector that allows to inject dependencies outside of constructor parameters.

All I had to do was to import Reflective injector from @angular/core.

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

And then:

let injector = ReflectiveInjector.resolveAndCreate([DrawingService]);
this.drawingApi = injector.get(DrawingService);

The class doesn't even have to be decorated with the @Injectable decorator. The only problem is that I have to provide all dependencies for DrawingService and all the nested dependencies, so that is hard to maintain.

EDIT:

Angular 5

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

const injector = Injector.create([
    { provide: DrawingService }
]);
this.drawingApi = injector.get(DrawingService);

Angular 6

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

const injector = Injector.create({ 
  providers: [ 
    { provide: DrawingService },
  ]
});
this.drawingApi = injector.get(DrawingService);
mdaniel
  • 31,240
  • 5
  • 55
  • 58
Liplattaa
  • 1,231
  • 2
  • 11
  • 10
  • Interesting to know. It would be nice, if one could use it as a pattern e.g. for injecting services into abstract classes. Otherwise you have to stuff the services into the super call of all concrete implementations of that abstract class. But as you told already, importing and providing (and maintaining by hand) all dependencies is also far from beeing elegant. – westor Sep 28 '16 at 06:52
  • 1
    Hello, I'm having the same issue as you, but the service I'm trying to inject uses `Http` and I keep getting the following error. `zone.js:355 Unhandled Promise rejection: No provider for Http!` I assume that I have to provide the dependency as you stated here "The class doesn't even have to be decorated with @Injectable decorator. The only problem is that I have to provide all dependencies for DrawingService and all the nested dependencies, so that is hard to maintain." But how do provide all those dependencies to a class with no @Component decorator? – Gustavo Nov 10 '16 at 23:19
  • 1
    So I went to angular documentation, and I eneded up doing this `ReflectiveInjector.resolveAndCreate([MyService, Http]);` It no longer breaks for missing Http, but I had to inlcude also Connection Backend, and then it asked for RequestOptions. Is this what you meant with `all nested dependencies, so that is hard to maintain`? – Gustavo Nov 10 '16 at 23:49
  • @Gustavo Did you ever resolve `Unhandled Promise rejection: No provider for Http!`? Having this issue now. – Zze Feb 13 '17 at 22:11
  • Yes and no, I successfully injected Http provider, but then it asked for other dependencies and they I added them and then those dependencies needed other dependencies and it was a never ending game, so I took a different approach to solve my problems. – Gustavo Mar 29 '17 at 18:19
  • 1
    note the deprecation https://angular.io/api/core/ReflectiveInjector#deprecation-notes – Harold Feb 06 '18 at 12:29
20

Here are two other possible ways to achieve the desired or very similar result.

First approach - using a manager for your entities or non-service objects

You have one or more factory service(s) that is/are in charge of instantiating your objects.

This means that it can provide the required deps further on to objects, and doesn't require that you pass them on yourself.

For example, say you have entities as a class hierarchy:

abstract class Entity { }

class SomeEntity extends Entity { 
   ...
}

You can then have an EntityManager that is a service and can construct entities:

@Injectable()   // is a normal service, so DI is standard
class EntityManager {

  constructor(public http: Http) { }    // you can inject any services now

  create<E extends Entity>(entityType: { new(): E; }): E {
    const entity = new entityType();    // create a new object of that type
    entity.manager = this;              // set itself on the object so that that object can access the injected services like http - one can also just pass the services not the manager itself
    return entity;
  }

}

You can also have construction parameters if you want to (but they will not have any type information because create needs to work with all types of entities):

class SomeEntity extends Entity { 
   constructor(param1, param1) { ... }
}

// in EntityManager
create<E extends Entity>(entityType: { new(): E; }, ...params): E {
    const entity = new entityType(...params);
    ...
}

Your entities can now declare the manager:

abstract class Entity {
  manager: EntityManager;
}

And your entities can use it to do whatever:

class SomeEntity extends Entity {
  doSomething() {
    this.manager.http.request('...');
  }
}

Now every time you need to create an entity/object you use this manager. The EntityManager needs to be injected itself but the entities are free objects. But all angular code will start from a controller or service or such so it will be possible to inject the manager.

// service, controller, pipe, or any other angular-world code

constructor(private entityManager: EntityManager) {
    this.entity = entityManager.create(SomeEntity);
}

This approach can also be adapted to arbitrary objects. You don't need a class hierarchy, but with typescript this works better. It also makes sense to have some base class for your objects as you can re-use code this old fashion way as well, especially in a domain/object oriented approach.

PROS: This approach is safer because it still resides on the full DI hierarchy and there should be less unwanted side-effects.

CONS: The downside is that you can never use new ever again, nor can you obtain access to these services in arbitrary code. You always need to rely on the DI and on your factory / factories.

Second approach - h4ckz0rs

You create a service dedicated to obtaining (via DI) the services you need in objects.

This part is very similar to the first approach, just that this service is not a factory. Instead, it passes on the injected services into an object that is defined outside this class in a different file (explanation after). For example:

...
import { externalServices } from './external-services';

@Injectable()
export class ExternalServicesService {

  constructor(http: Http, router: Router, someService: SomeService, ...) {
    externalServices.http = http;
    externalServices.router = router;
    externalServices.someService = someService;
  }

}

The object that will hold the services is defined in its own file as such:

export const externalServices: {
  http,
  router,
  someService
} = { } as any;

Note that the services are not using any type information (this is a drawback but necessary).

Then, you must make sure that ExternalServicesService gets injected once. Best place is to use the main app component:

export class AppComponent {

  constructor(..., externalServicesService: ExternalServicesService) {

Finally, now you can use the services in any arbitrary object at any point after the main app component has been instantiated.

import { externalServices } from '../common/externalServices' // or wherever is defined

export class SomeObject() {
    doSomething() {
        externalServices.http().request(...) // note this will be called after ng2 app is ready for sure
    }
}

Note you'll not be able to call any of these services in the class code or in objects not instantiated after the app is instantiated. But in a typical app, this should never be needed.

Now, a few explanations about this weird setup:

Why use an object externalServices in a separate file instead of the same file or simply saving the services on the class itself (as static attributes) and why are the services untyped?

The reason is that when you're bulding the code e.g. via angular-cli / webpack with --prod mode, it's very likely to get cyclic dependencies that cannot be resolved correctly and you'll get ugly errors that are difficult to find - I've already been through this :).

An error of the form

Cannot read property 'prototype' of undefined

seen only when running with --prod flag will hint to the fact that dependencies are not resolved correctly.

It's much better to make sure that ExternalServicesService only depends on externalServices to pass the service instances, and the application only injects ExternalServicesService once (e.g. in your main AppComponent) then all arbitrary code / objects will only use externalServices to obtain the services.

Thus any such code will only need to import the externalServices which has no further deps (because the services are also not typed). If they were to import ExternalServicesService it would have imported everything else and would not have been able to resolve the bi-directional deps statically. And this becomes a major problem in ng2/webpack when bundling for prod.

The same would happen if we were to use types for the services, because that will require imports.

PROS: This approach is easier to use once the setup has been made, and you are free to use new. Basically any code file can import the externalServices and have instant access to those services you want to expose this way.

CONS: The downside is the hackish setup and the possible issues caused by cyclic deps. It is also more sensitive, as you cannot be sure externalServices has those services right away. They will only be defined once the ng2 app starts and the ExternalServicesService is first injected. A drawback is also that you no longer have type information on those services.


PS: I'm not sure why this topic is not more popular.

For example, being a fan of domain-oriented design, having powerful entities (e.g. with methods directed at REST calls or interacting with other services) is important and this limitation always made it difficult.

We had to overcome this limitation both in angularjs and now again in Angular2+ as it seems still not to be addressed in the library.

Ovidiu Dolha
  • 5,335
  • 1
  • 21
  • 30
  • 7
    Regarding your "PS": I absolutely agree with you. There are so many subtleties, low tech featueres, ... in angular. But when it comes to real word problems as you've outlined angular doesn't provide adequate support. – Karl May 17 '17 at 17:49
6

As of Angular 5.x:

import { Injector } from "@angular/core";
export class Model {

    static api: Api;

    constructor(data: any) {

        // check the api ref not exist
        // We don't want to initiate a new object every time
        if (!Model.api){
            //try inject my api service which use the HttpClient
            const injector: any = Injector.create([{ provide: Api, useClass: Api, deps: [] }]);
            Model.api = injector.get(Api);
        }

        // .....

    }
}
Eymen Elkum
  • 3,013
  • 1
  • 20
  • 34
0

There is a post from vojtajina on GitHub that provides a nice approach to this problem. This answer is just a link, but it is really better to read this in its context since there are other interesting information there:

https://github.com/angular/di.js/issues/22#issuecomment-36773343

ndvo
  • 939
  • 11
  • 16
-3

In fact, you can't. The class must be decorated with @Injectable to let Angular2 inject things. The@Inject decorator is "only" there to specify additional metadata about what to inject.

In your case, the class is managed by you since most of its constructor parameters don't correspond to dependencies and are provided when you explicitly instantiate the class.

Thierry Templier
  • 198,364
  • 44
  • 396
  • 360