9

I have an Angular2 DI question. Say I have a TestService and I want to use 2 different instances of this service inside the same component. If I simply add a provider to the component and I add the 2 instances to the constructor I end up with the same service instance. For example:

TestService

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

@Injectable()
export class TestService {

    public id: number = Math.random();

    public toString(): string {
        return "Id: " + this.id;
    }
}

Test component

import {Component, Input, OnInit} from "@angular/core";
import {TestService} from "../../services/test.service";

@Component({
    providers: [TestService]
})
export class TestComponent implements OnInit {

    constructor(private _testService1: TestService, private _testService2: TestService) { };

    ngOnInit() {
        console.log("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", this._testService1.toString());
        console.log("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", this._testService2.toString());
    }
}

Result in console

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Id: 0.24242492129168425
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB Id: 0.24242492129168425

Can someone pls tell me if there's a way to use Angular2's DI mechanism to inject multiple different instances of a service within the same component or should I just drop the DI for this particular case and create my instances manually with a manual constructor?

Thanks in advance

3 Answers 3

13

Given that there is finite amount of instances, the straightforward way may be:

@Component({
    providers: [
        { provide: 'TestService1', useClass: TestService },
        { provide: 'TestService2', useClass: TestService }
    ]
})
export class TestComponent implements OnInit {
    constructor(
        @Inject('TestService1') private _testService1: TestService,
        @Inject('TestService2') private _testService2: TestService
    ) {}
    ...
}

Or OpaqueToken counterpart to avoid overriding services with the same string identifier:

export const TestService1 = new OpaqueToken;
export const TestService2 = new OpaqueToken;

...
providers: [
    { provide: TestService1, useClass: TestService },
    { provide: TestService2, useClass: TestService }
]
...
constructor(
    @Inject(TestService1) private _testService1: TestService,
    @Inject(TestService2) private _testService2: TestService
) {}

It doesn't hurt DI in TestService constructor. And keeps the testability of TestComponent on par, both service instances can be mocked independently.

Sign up to request clarification or add additional context in comments.

2 Comments

Great answer. To complete, I would say OpaqueToken should also be used instead of the strings to avoid naming collisions: angular.io/docs/ts/latest/api/core/index/OpaqueToken-class.html
@CharlesHETIER Thanks, string identifiers were used here for brevity, but it doesn't hurt to mention that OpaqueToken can be used as well.
6

You can inject a factory that returns a new instance every time you call it:

@NgModule({
   providers: [{
      provide: 'testService', 
      useFactory: (/* TestService deps here like `http`*/) => 
        (/* params */) => new TestService(/* http */), 
      deps: [/* TestService deps here like `Http`*/ ]
    }]
})


@Component(...)
export class TestComponent implements OnInit {

    constructor(@Inject('testService') private _testServiceFactory) { };

    ngOnInit() {
        console.log("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", this._testServiceFactory( /* params */).toString());
        console.log("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", this._testServiceFactory().toString());
    }
}

Plunker example (check the output in the browser console when you click the button)

11 Comments

It's simply not doing the work. If you inject with useFactory, the factory will be used only once for injecting into _testServiceFactory. But this snippet will not going to work anyway, as you do not have type for the constructor argument, angular can't know what to inject.
@TimKachko thanks a lot for your valuable feedback! I updated my answer. You are right, about the type. I changed it to use a string key instead with @Inject(). The factory will work though. Perhaps I could make it more obvious in the code, but the factory is a function that returns a function, therefore DI will keep an instance of the returned function and pass it to every class that injects 'testService', and because a function is passed to the constructor that returns a new TestService() every time it is called, this will work.
Let you just try to get work it in the Plucker you'll see what I mean. the useFactory will inject TestService, not a factory method which you can use to create the TestService.
Plunker doesn't work well with my Chrome version since a while :-/. Did you see the useFactory: (...) => (...) => new TestService() (double () =>) where the value returned by the factory is a factory function?
it has only one ()=> and therefore returns a class instance, while my example (double ()=>)returns a function
|
2

I would create a static method that returns new instance in the service, and inject it only one through DI in component. Something like:

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

@Injectable()
export class TestService {

    public id: number = Math.random();

    public toString(): string {
        return "Id: " + this.id;
    }
    static init() {
      return new TestService();
    }
}

then in component:

import {Component, Input, OnInit} from "@angular/core";
import {TestService} from "../../services/test.service";

@Component({
    providers: [TestService]
})
export class TestComponent implements OnInit {
    _testService1: TestService;
    _testService2: TestService;

    constructor(_testFactory: TestService) { 
       this._testService1 = _testFactory.init();
       this._testService2 = _testFactory.init();
    };

    ngOnInit() {
        console.log("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", this._testService1.toString());
        console.log("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", this._testService2.toString());
    }
}

2 Comments

This also fits the coding style from the Angular 2 team's docs.
The thing is where do you store these two instances in case other components need them.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.