2

I would like to have some explanations about singleton services in Angular2. I made a quick example in a plnkr : http://plnkr.co/edit/09XVxN?p=preview

Try to change the displayed component by clicking on both buttons. You will see in the debugger console ServiceA constructor console.log messages.

Why AService in this case isn't a singleton ? Because each time we switch the displayed component it calls the AService constructor...

Thanks by advance

Romain
  • 801
  • 1
  • 6
  • 13

2 Answers2

1

Since Angular 9

Use @Injectable({ providedIn: 'root' })

With providedIn: '...' the service will discovered by the Angular compiler. It is then not necessary to add the service to providers anymore.

https://angular.io/guide/providers

Original answer

When you add a service to the providers: list of a component you get a new instance for every component.
Add it only to bootstrap(AppComponent, [AService]) and the whole application gets the same reference.

In Angulars DI every provided instance is a singleton, but only within the scope of the injector that created the instance.
Angulars DI is hierarchical. For each component a child-injector is created. DI starts with the closest injector to resolve the required type. If there is a provider but no instance yet, one will created. If there is no provider DI iterates to the parent injector until it finds one or until it reaches the root injector. If it reached the root injector and still didn't find a provider it throws.

The providers added to bootstrap() are the providers for the root injector and therefore applicable for the whole application when not further down the hierarchy another injector has a provider for the same type registered.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Would you kindly update this to be relevant to Angular 7? Specifically, how to ensure `ctor()` is only called once(i.e. singleton)? – ttugates Mar 07 '19 at 18:22
  • Only provide it in a module that is **not** lazy-loaded and you should be fine or if you have to provide it in a lazy-loaded module, provide it in `forRoot` (you should be able to find more about that) to ensure the service provided in a non-lazy-loaded module anyway. If you provide it also on a component or directive then you again can get different instances, so do not do that if you want a singleton. – Günter Zöchbauer Mar 07 '19 at 18:24
  • So would decorating the service with `@Injectable({ providedIn: 'root' })` ensure its a singleton? Since that provides for root injector. – ttugates Mar 07 '19 at 18:25
  • I haven't used Angular 7 and don't know what `providedIn` does. According to the docs it seems to do something like `forRoot`. – Günter Zöchbauer Mar 07 '19 at 18:27
  • 1
    Its added to service created by Angular CLI as of Angular 6. Thanks for help tho.. See: https://angular.io/guide/providers#providing-a-service – ttugates Mar 07 '19 at 18:29
  • @GünterZöchbauer no `providedIn` is not the same as `forRoot`. as already mentioned this does not work for lazy loaded modules because they will create their own injector. I've written a summary here https://developapa.com/angular-singleton-service/ – Nicolas Gehlert Nov 10 '22 at 13:52
  • @NicolasGehlert I think you are wrong. `providedIn` root also adds the provider to the root injector even when provided in a lazy loaded module. The Angular compiler figures this out at build time already https://stackoverflow.com/a/53010605/217408 – Günter Zöchbauer Nov 10 '22 at 15:01
  • @GünterZöchbauer no it does not. lazy loading modules create their own injector. check up in the blog article I mentioned. I put together a very simple stack blitz example: https://stackblitz.com/edit/angular-p9z1gs?file=src/app/app.service.ts You can see that the App data service has provided in root set but actually returns different data in both lazy loaded modules – Nicolas Gehlert Nov 14 '22 at 09:16
  • @NicolasGehlert That's because you provide it in the `customers.module and `orders.module` which overrides the provider in the applications root scope where it's placed by `@Injectable({ providedIn: 'root', })`. Remove both `providers: [SingletonService],` and you get the behavior explained in the docs. – Günter Zöchbauer Nov 14 '22 at 10:14
  • I know! :) That's what I explained earlier. There are different approaches. But providedIn: root is not the same as forRoot and providedIn is NOT creating a singleton instance. And forRoot is another approach to the single providers in the main module. – Nicolas Gehlert Nov 14 '22 at 10:50
  • For more information check the angular docs on the the topic lazy loaded modules: https://angular.io/guide/ngmodule-faq#why-is-a-service-provided-in-a-lazy-loaded-module-visible-only-to-that-module – Nicolas Gehlert Nov 14 '22 at 10:51
  • @NicolasGehlert `forRoot` and `providedIn: 'root'` is the same, you are just using it wrongly. – Günter Zöchbauer Nov 14 '22 at 16:29
  • @GünterZöchbauer could you please just read the documentation? I can link you the important part "When the Angular router lazy-loads a module, it creates a new execution context. That context has its own injector, which is a direct child of the application injector." Its own injector. providedIn root will only create it in the own injector not the parent one of the main application – Nicolas Gehlert Nov 15 '22 at 07:53
  • @NicolasGehlert I know that since about 6 years. That's what `forRoot` and `provideIn: 'root'` are supposed to work around. If you'd read my comments and applied the change to your stackblitz, you'd understand as well. – Günter Zöchbauer Nov 15 '22 at 08:09
  • @GünterZöchbauer I saw your comments ;) just wanted to point out that forRoot and providedIn root are not doing the same thing. and this is by design. the only flaw there is the naming "root" because it's suggest its the application root but actually it's the injector root and there can be multiple 'root injectors' with lazy loaded modules – Nicolas Gehlert Nov 15 '22 at 08:24
1

In fact, it depends on where you put the corresponding providers:

  • If you put it at the bootstrap level, you'll have a single instance for the whole application

    bootstrap(AppComponent, [ MyService ]);
    
  • If you put it at the component level, you'll have an instance for each component instance.

    @Component({
      (...)
      providers: [ MyService ]
    })
    export class MyComponent {
      (...)
    }
    

This is because of the hierarchical injectors of Angular2. You can make a bit finer since it's possible to use providers from parent component as well...

This answer could give you more details about this:

Community
  • 1
  • 1
Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • update: the new syntax `platformBrowserDynamic().bootstrapModule(AppModule)` where app module would provide `MyService ` – Wilhelmina Lohan Mar 07 '19 at 18:19
  • Would you kindly update this to be relevant to Angular 7? Or I can ask another question at risk of being marked as duplicate. Specifically, how to ensure `ctor()` is only called once(i.e. singleton)? – ttugates Mar 07 '19 at 18:22