27

I have created AngularJS 2 service and use it in 2 differents components : App-Component & Sub-Component. Each one output property 'log' (a string) of my service.

StateService class :

@Injectable ()
class StateService {

    public log : string;
    static count : number = 0;

    constructor () {
        this.log = '';
        StateService.count++;
        this.writeToLog ('CREATED '+StateService.count+' at ' + new Date().toString());
    }

    public writeToLog (text : string) : void {
        this.log += text + '\n';
    }
}  

Component :

@Component ({
    selector : 'Sub-Component',
    template : `<hr>
            This is the Sub-Component !
            <BR>
            StateService Log : 
            <pre>{{ _stateService.log }}</pre>
            <button (click)="WriteToLog ()">Write to log</button>
            `,
    providers : [StateService]
})

export class SubComponent {
    constructor (private _stateService : StateService) {
    }

    public WriteToLog () : void {
        this._stateService.writeToLog ('From Sub-Component - This is '+new Date().toString());
    }
}

Live example of code here

I except that service is created once and when each component call WriteToLog method, the output is the same in each component but it's not.

Example of output :

The App-Component can output this :

Instance 1 - Created at Thu Jan 21 2016 11:43:51

From App-Component - This is Thu Jan 21 2016 11:43:54

From App-Component - This is Thu Jan 21 2016 11:43:55

and the Sub-Component can output this :

Instance 2 - Created at Thu Jan 21 2016 11:43:51

From Sub-Component - This is Thu Jan 21 2016 11:43:57

From Sub-Component - This is Thu Jan 21 2016 11:43:58

So it appear that 2 instance of service is created (instance 1 + instance 2)

I only want one instance ;) and when I append string in log, this must appear in both component.

Thank you for your help

Community
  • 1
  • 1
Philippe sillon
  • 1,572
  • 2
  • 14
  • 20

2 Answers2

32

update Angular >= 2.0.0-RC.6

Don't add the service to the providers of the component. Instead add it to

@NgModule({ providers: [...], ...

(of a module that is not lazy loaded because lazy loaded modules introduce their own scope)

@Component ({
    selector : 'Sub-Component',
    template : `<hr>
            This is the Sub-Component !
            <BR>
            StateService Log : 
            <pre>{{ _stateService.log }}</pre>
            <button (click)="WriteToLog ()">Write to log</button>
            `,
    // providers : [StateService] <== remove
})

Angular <=2.0.0-RC.5

If you add it on a component you get a new service instance for each component instance. Instead add it to

bootstrap(AppComponent, [StateService]);

You can have more fine-grained control by adding it to a single component, then this component and all children get the same instance injected but otherwise the application works with the instance created by bootstrap(). This is the "hierarchical" in Angulars DI.

See also
- http://blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular-2.html
- http://blog.thoughtram.io/angular/2015/09/17/resolve-service-dependencies-in-angular-2.html

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • +1 I''ve found it [here](https://angular.io/docs/ts/latest/api/core/index/NgModule-interface.html#!#bootstrap-anchor) - but it is not clear that this is the place that I should put the call to the service. also - without testing - where does it say that each component being injected with a service - will create a new instance ? – Royi Namir Apr 03 '17 at 15:47
  • 2
    Good question. I haven't seen it explicitly mentioned in the docs, but there are a lot of docs and I haven't read them all. I learned it from discussions during development of AngularDart 1.0. DI maintains a single instance per provider. Every provider instance leads to one service instance. This is why you get multiple instances if you provide a service on a component that is added multiple times to your application. For providers added globally (AppComponent, NgModule), there will ever be only a single instance. – Günter Zöchbauer Apr 03 '17 at 15:53
  • @GünterZöchbauer I've tried it with the latest angular version and I can't find the place to put it so it can run once ( see console) https://plnkr.co/edit/LvO6yVOQjKoCVogK7HRh?p=preview – Royi Namir Apr 03 '17 at 18:16
  • 1
    If you provide it in the `AppModule` (and only there) it's the safest place to be a singleton. You can also provide it at an imported `@NgModule()`, but here you have to take special precautions if it is a lazy loaded module. https://plnkr.co/edit/JYZrPMvIdrtMtbltbMIz?p=preview – Günter Zöchbauer Apr 03 '17 at 18:19
  • @GünterZöchbauer I've added it as `bootstrap: [ AppComponent , ExampleService ]` ,and there's an error https://plnkr.co/edit/RnDdc29A4j1Kit4VZqdQ?p=preview – Royi Namir Apr 03 '17 at 18:21
  • I made it more clear in my answer. Adding providers to `bootstrap()` is only for older Angular2 versions. – Günter Zöchbauer Apr 03 '17 at 18:23
  • 2
    If you want a single instance for your whole application, put it in `@NgModule(...)`. If you want a single instance for each component instance (and it's children) and the service should not be available outside the component, then put it in the `@Component(...)` – Günter Zöchbauer Apr 03 '17 at 18:29
  • Do we know why if I provide a service only once *(even with `forRoot()`)* in a parent module it will get instanciated 2 times: 1 when parent instances and another when child instances? – SrAxi Apr 13 '17 at 13:03
  • Probably not without seeing a Plunker that allows to reproduce. – Günter Zöchbauer Apr 13 '17 at 13:04
3

In addition to the Günter's great answer, this link could perhaps give more details about how the hierarchical dependency injection of Angular2 works:

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