Angular 2: Inject service into class Angular 2: Inject service into class angular angular

Angular 2: Inject service into class


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);


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 standardclass 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 EntityManagercreate<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 codeconstructor(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 definedexport 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.


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);        }        // .....    }}