How do I create a singleton service in Angular 2?
Asked Answered
B

16

163

I've read that injecting when bootstrapping should have all children share the same instance, but my main and header components (main app includes header component and router-outlet) are each getting a separate instance of my services.

I have a FacebookService which I use to make calls to the facebook javascript api and a UserService that uses the FacebookService. Here's my bootstrap:

bootstrap(MainAppComponent, [ROUTER_PROVIDERS, UserService, FacebookService]);

From my logging it looks like the bootstrap call finishes, then I see the FacebookService then UserService being created before the code in each of the constructors runs, the MainAppComponent, the HeaderComponent and the DefaultComponent:

enter image description here

Barbusse answered 24/3, 2016 at 11:10 Comment(1)
You're sure you have not added UserService and FacebookService to providers anywhere else?Hersey
H
134

Jason is completely right! It's caused by the way dependency injection works. It's based on hierarchical injectors.

There are several injectors within an Angular2 application:

  • The root one you configure when bootstrapping your application
  • An injector per component. If you use a component inside another one. The component injector is a child of the parent component one. The application component (the one you specify when boostrapping your application) has the root injector as parent one).

When Angular2 tries to inject something in the component constructor:

  • It looks into the injector associated with the component. If there is matching one, it will use it to get the corresponding instance. This instance is lazily created and is a singleton for this injector.
  • If there is no provider at this level, it will look at the parent injector (and so on).

So if you want to have a singleton for the whole application, you need to have the provider defined either at the level of the root injector or the application component injector.

But Angular2 will look at the injector tree from the bottom. This means that the provider at the lowest level will be used and the scope of the associated instance will be this level.

See this question for more details:

Hannibal answered 24/3, 2016 at 12:17 Comment(6)
Thanks, that explains it well. It was just counter-intuitive for me because that kinda breaks with angular 2's self-contained component paradigm. So let's say I'm making a library of components for Facebook, but I want them to all use a singleton service. Maybe there's a component to show the profile picture of the logged in user, and another one to post. The app that uses those components has to include the Facebook service as a provider even if it doesn't use the service itself? I could just return a service with a 'getInstance()' method that manages the singleton of the real service...Barbusse
@tThierryTemplier How would i do the opposite, i have a common service class i want to inject in multiple component but instantiate a new instance every time (providers/directives options is supposed to be deprecated and removed in the next release)Warrantee
Sorry for being so stupid but it's not clear for me how do you create a singleton service, can you explain in more detail?Hither
So, to work with a single instance of a service, should it be declared as provider in app.module.ts or in app.component.ts ?Cutup
Declaring each service in app.module.ts only, did the job for me.Cutup
Your answer was extremely helpful! Previously I was declaring the provider in two components and adding in just a parent module solved my issue with singleton service!Marquee
B
166

Update (Angular 6 +)

The recommended way to create a singleton service has changed. It is now recommended to specify in the @Injectable decorator on the service that it should be provided in the 'root'. This makes a lot of sense to me and there's no need to list all the provided services in your modules at all anymore. You just import the services when you need them and they register themselves in the proper place. You can also specify a module so it will only be provided if the module is imported.

@Injectable({
  providedIn: 'root',
})
export class ApiService {
}

Update (Angular 2)

With NgModule, the way to do it now I think is to create a 'CoreModule' with your service class in it, and list the service in the module's providers. Then you import the core module in your main app module which will provide the one instance to any children requesting that class in their constructors:

CoreModule.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ApiService } from './api.service';

@NgModule({
    imports: [
        CommonModule
    ],
    exports: [ // components that we want to make available
    ],
    declarations: [ // components for use in THIS module
    ],
    providers: [ // singleton services
        ApiService,
    ]
})
export class CoreModule { }

AppModule.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AppComponent } from './app.component';
import { CoreModule } from './core/core.module';

@NgModule({
    declarations: [ AppComponent ],
    imports: [
        CommonModule,
        CoreModule // will provide ApiService
    ],
    providers: [],
    bootstrap: [ AppComponent ]
})
export class AppModule { }

Original Answer

If you list a provider in bootstrap(), you don't need to list them in your component decorator:

import { ApiService } from '../core/api-service';

@Component({
    selector: 'main-app',
    templateUrl: '/views/main-app.html',
    // DO NOT LIST PROVIDERS HERE IF THEY ARE IN bootstrap()!
    // (unless you want a new instance)
    //providers: [ApiService]
})
export class MainAppComponent {
    constructor(private api: ApiService) {}
}

In fact listing your class in 'providers' creates a new instance of it, if any parent component already lists it then the children don't need to, and if they do they will get a new instance.

Barbusse answered 24/3, 2016 at 11:24 Comment(5)
As of now (January 2017), you'd list the service under [providers] in your module file, not in bootstrap(), right?Tittup
Why not put ApiService in AppModule's providers, and thus avoid the need for CoreModule? (Not sure this is what @JasonSwett is suggesting.)Caiman
@JasonGoemaat Can you add the example how you use it in the component? We could import the ApiService on the top but why then bother at all to put it in the providers array of the CoreModule and then import that one in app.module... it didn't click for me, yet.Whizbang
So, putting the service on module provider will provide a singleton for that module. And putting the service on the Component providers will create a new instance for each instance of the component? Is that right?Isabellaisabelle
@Isabellaisabelle I created a test app to help show what is happening. Interesting that even though TestService is specified in both core and app modules, instances aren't created for the modules because it is provided by the component so angular never gets that high up the injector tree. So if you provide a service in your module and never use it, an instance doesn't seem to be created.Barbusse
H
134

Jason is completely right! It's caused by the way dependency injection works. It's based on hierarchical injectors.

There are several injectors within an Angular2 application:

  • The root one you configure when bootstrapping your application
  • An injector per component. If you use a component inside another one. The component injector is a child of the parent component one. The application component (the one you specify when boostrapping your application) has the root injector as parent one).

When Angular2 tries to inject something in the component constructor:

  • It looks into the injector associated with the component. If there is matching one, it will use it to get the corresponding instance. This instance is lazily created and is a singleton for this injector.
  • If there is no provider at this level, it will look at the parent injector (and so on).

So if you want to have a singleton for the whole application, you need to have the provider defined either at the level of the root injector or the application component injector.

But Angular2 will look at the injector tree from the bottom. This means that the provider at the lowest level will be used and the scope of the associated instance will be this level.

See this question for more details:

Hannibal answered 24/3, 2016 at 12:17 Comment(6)
Thanks, that explains it well. It was just counter-intuitive for me because that kinda breaks with angular 2's self-contained component paradigm. So let's say I'm making a library of components for Facebook, but I want them to all use a singleton service. Maybe there's a component to show the profile picture of the logged in user, and another one to post. The app that uses those components has to include the Facebook service as a provider even if it doesn't use the service itself? I could just return a service with a 'getInstance()' method that manages the singleton of the real service...Barbusse
@tThierryTemplier How would i do the opposite, i have a common service class i want to inject in multiple component but instantiate a new instance every time (providers/directives options is supposed to be deprecated and removed in the next release)Warrantee
Sorry for being so stupid but it's not clear for me how do you create a singleton service, can you explain in more detail?Hither
So, to work with a single instance of a service, should it be declared as provider in app.module.ts or in app.component.ts ?Cutup
Declaring each service in app.module.ts only, did the job for me.Cutup
Your answer was extremely helpful! Previously I was declaring the provider in two components and adding in just a parent module solved my issue with singleton service!Marquee
G
26

I know angular has hierarchical injectors like Thierry said.

But I have another option here in case you find a use-case where you don't really want to inject it at the parent.

We can achieve that by creating an instance of the service, and on provide always return that.

import { provide, Injectable } from '@angular/core';
import { Http } from '@angular/core'; //Dummy example of dependencies

@Injectable()
export class YourService {
  private static instance: YourService = null;

  // Return the instance of the service
  public static getInstance(http: Http): YourService {
    if (YourService.instance === null) {
       YourService.instance = new YourService(http);
    }
    return YourService.instance;
  }

  constructor(private http: Http) {}
}

export const YOUR_SERVICE_PROVIDER = [
  provide(YourService, {
    deps: [Http],
    useFactory: (http: Http): YourService => {
      return YourService.getInstance(http);
    }
  })
];

And then on your component you use your custom provide method.

@Component({
  providers: [YOUR_SERVICE_PROVIDER]
})

And you should have a singleton service without depending on the hierarchical injectors.

I'm not saying this is a better way, is just in case someone has a problem where hierarchical injectors aren't possible.

Granthem answered 7/6, 2016 at 8:59 Comment(8)
Should the SHARE_SERVICE_PROVIDER be YOUR_SERVICE_PROVIDER in the component? Also I'm assuming that importing the service file is needed like normal and the constructor will still have a parameter of type 'YourService', right? I like this I think, allows you to guarantee a singleton and not have to make sure the service is provided up the hierarchy. It also allows individual components to get their own copy by just listing the service in providers instead of the singleton provider, right?Barbusse
@JasonGoemaat you are right. Edited that. Exactly, you do it exactly the same way in the constructor and on the providers of that component you add YOUR_SERVICE_PROVIDER. Yes, all components will get the same instance by just adding it in the providers.Granthem
At the point a developer uses this, they should be asking thamself "why am I even using Angular at all if I'm not going to use the mechanisms Angular provides for this situation?" Providing the service at the application level should be sufficient for every situation within the context of Angular.Twickenham
@Twickenham I've never said this is a must do, it was just a suggestion of another option for anyone who's looking into creating a singleton without depending on the parent provider. Why is this outside of Angular context if we are still using angular provide() and providers?Granthem
+1 Although this is a way to create singleton services it serves very nicely as a way of creating a multiton service by simply changing the instance property into a key-value map of instancesSheena
@Twickenham I can imagine an app that does not require a service for every route, or a reusable module that wants a static instance and wants to use the same instance with any other modules that uses it. Maybe something that creates a web socket connection to the server and processes messages. Maybe only a few routes in the app would want to use it so no need to create a service instance and a web socket connection when the app starts if it's not needed. You can program around that to have the components 'init' the service everywhere it is used, but on demand singletons would be useful.Barbusse
This answer should have 999999 votes, epic stuff, thanks a lot!Interviewer
What does shareService in the instance definition represent? I can't figure it out and indeed, my TSLint complains it cannot find the name.Paraclete
H
16

Syntax has been changed. Check this link

Dependencies are singletons within the scope of an injector. In below example, a single HeroService instance is shared among the HeroesComponent and its HeroListComponent children.

Step 1. Create singleton class with @Injectable decorator

@Injectable()
export class HeroService {
  getHeroes() { return HEROES;  }
}

Step 2. Inject in constructor

export class HeroListComponent { 
  constructor(heroService: HeroService) {
    this.heroes = heroService.getHeroes();
  }

Step 3. Register provider

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    routing,
    HttpModule,
    JsonpModule
  ],
  declarations: [
    AppComponent,
    HeroesComponent,
    routedComponents
  ],
  providers: [
    HeroService
  ],
  bootstrap: [
    AppComponent
  ]
})
export class AppModule { }
Hoeg answered 1/10, 2016 at 0:27 Comment(2)
what if my Injectable class isn't a service and just contains static strings for global use?Lucrecialucretia
like this providers: [{provide:'API_URL',useValue: 'coolapi.com'}]Cenac
B
8

this seems to be working well for me

@Injectable()
export class MyStaticService {
  static instance: MyStaticService;

  constructor() {
    return MyStaticService.instance = MyStaticService.instance || this;
  }
}
Blizzard answered 31/7, 2016 at 20:17 Comment(5)
I'd call this an Angular2 anti-pattern. Provide the service correctly and Angular2 will always inject the same instance. See also #12756039Hersey
@Günter Zöchbauer , could please give some advise regarding "Provide the service correctly and Angular2 will always inject the same instance." ? Because it is unclearly and I could not find any usefull information by googling.Rules
I just posted this answer that might help with your question https://mcmap.net/q/151753/-alternative-to-scope-in-angular-2-0 (see also the link there)Hersey
This is perfect. You should use angulars own dependency injection, but there's no harm in using this pattern to be absolutely certain your service is a singleton when you expect it to be. Potentially saves a lot of time hunting for bugs just because you inject the same service in two different places.Kirk
I used this pattern to ascertain that the issue I was facing was because of service not being singletonCorncrib
G
7

Adding @Injectable decorator to the Service, AND registering it as a provider in the Root Module will make it a singleton.

Gert answered 2/10, 2017 at 11:45 Comment(4)
Just tell me if I am understanding it. If I do what you said, ok, it will be a singleton. If, besides that, the service is also a provider in another module, it will no longer be a singleton, right? Because of the hierarchy.Antiknock
And do not register the provider in @Component decorator of the pages.Finesse
@Laura. Do I still import it into components that actually use the service?Nihil
@Nihil Yes, you need to import it, and then you only need to declare it in the constructor like this: import { SomeService } from '../../services/some/some'; @Component({ selector: 'page-selector', templateUrl: 'page.html', }) export class SomePage { constructor( public someService: SomeService ) { } Finesse
C
5

Here is a working example with Angular version 2.3. Just call the constructor of the service the stand way like this constructor(private _userService:UserService) . And it will create a singleton for the app.

user.service.ts

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import { Subject }    from 'rxjs/Subject';
import { User } from '../object/user';


@Injectable()
export class UserService {
    private userChangedSource;
    public observableEvents;
    loggedUser:User;

    constructor() {
       this.userChangedSource = new Subject<any>();
       this.observableEvents = this.userChangedSource.asObservable();
    }

    userLoggedIn(user:User) {
        this.loggedUser = user;
        this.userChangedSource.next(user);
    }

    ...
}

app.component.ts

import { Component } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { UserService } from '../service/user.service';
import { User } from '../object/user';

@Component({
    selector: 'myApp',
    templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
    loggedUser:User;

    constructor(private _userService:UserService) { 
        this._userService.observableEvents.subscribe(user => {
                this.loggedUser = user;
                console.log("event triggered");
        });
    }
    ...
}
Conidiophore answered 12/1, 2017 at 7:6 Comment(0)
C
3

You can use useValue in providers

import { MyService } from './my.service';

@NgModule({
...
  providers: [ { provide: MyService, useValue: new MyService() } ],
...
})
Cocklebur answered 22/1, 2017 at 20:50 Comment(1)
useValue is not related to singleton. Usevalue is just to pass a value instead of a Type (useClass) that DI calls new on or useFactory where a function is passed that returns the value when called by DI. Angular DI maintains a single instance per provider automatically. Only provide it once and you have a singleton. Sorry, I have to downvote because it's just invalid information :-/Hersey
F
3

From Angular@6, you can have providedIn in an Injectable.

@Injectable({
  providedIn: 'root'
})
export class UserService {

}

Check the docs here

There are two ways to make a service a singleton in Angular:

  1. Declare that the service should be provided in the application root.
  2. Include the service in the AppModule or in a module that is only imported by the AppModule.

Beginning with Angular 6.0, the preferred way to create a singleton services is to specify on the service that it should be provided in the application root. This is done by setting providedIn to root on the service's @Injectable decorator:

Fast answered 10/5, 2018 at 8:53 Comment(1)
This is good, but you may also have unexpected problems with variables not being there that might be resolved by declaring some items public static.Splanchnology
C
2

Just declare your service as provider in app.module.ts only.

It did the job for me.

providers: [Topic1Service,Topic2Service,...,TopicNService],

then either instanciate it using a constructor private parameter :

constructor(private topicService: TopicService) { }

or since if your service is used from html, the -prod option will claim:

Property 'topicService' is private and only accessible within class 'SomeComponent'.

add a member for your service and fill it with the instance recieved in the constructor:

export class SomeComponent {
  topicService: TopicService;
  constructor(private topicService: TopicService) { 
    this.topicService= topicService;
  }
}
Cutup answered 15/1, 2018 at 20:51 Comment(0)
H
2

A singleton service is a service for which only one instance exists in an app.

There are (2) ways to provide a singleton service for your application.

  1. use the providedIn property, or

  2. provide the module directly in the AppModule of the application

Using providedIn

Beginning with Angular 6.0, the preferred way to create a singleton service is to set providedIn to root on the service's @Injectable() decorator. This tells Angular to provide the service in the application root.

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

@Injectable({
  providedIn: 'root',
})
export class UserService {
}

NgModule providers array

In apps built with Angular versions prior to 6.0, services are registered NgModule providers arrays as follows:

@NgModule({
  ...
  providers: [UserService],
  ...
})

If this NgModule were the root AppModule, the UserService would be a singleton and available throughout the app. Though you may see it coded this way, using the providedIn property of the @Injectable() decorator on the service itself is preferable as of Angular 6.0 as it makes your services tree-shakable.

Hyperon answered 29/6, 2020 at 8:34 Comment(0)
D
1

Besides the great answers here, i would like to also point to this section on Angular's website:

TL;DR

When you provide the service at the root level, Angular creates a single, shared instance of HeroService and injects it into any class that asks for it. Registering the provider in the @Injectable() metadata also allows Angular to optimize an app by removing the service from the compiled application if it isn't used, a process known as tree-shaking.


3 ways to register a provider a.k.a service

You must register at least one provider of any service you are going to use. The provider can be part of the service's own metadata, making that service available everywhere, or you can register providers with specific modules or components. You register providers in the metadata of the service (in the @Injectable() decorator), or in the @NgModule() or @Component() metadata

1 Root injector

By default, the Angular CLI command ng generate service registers a provider with the root injector for your service by including provider metadata in the @Injectable() decorator. The tutorial uses this method to register the provider of HeroService class definition.

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

When you provide the service at the root level, Angular creates a single, shared instance of HeroService and injects it into any class that asks for it. Registering the provider in the @Injectable() metadata also allows Angular to optimize an app by removing the service from the compiled application if it isn't used, a process known as tree-shaking.

2 Module Level

When you register a provider with a specific NgModule, the same instance of a service is available to all components in that NgModule. To register at this level, use the providers property of the @NgModule() decorator.

@NgModule({
  providers: [
  BackendService,
  Logger
 ],
 …
})

3 Component Level

When you register a provider at the component level, you get a new instance of the service with each new instance of that component. At the component level, register a service provider in the providers property of the @Component() metadata.

@Component({
  selector:    'app-hero-list',
  templateUrl: './hero-list.component.html',
  providers:  [ HeroService ]
})
Downswing answered 6/7, 2023 at 3:44 Comment(0)
V
0
  1. If you want to make service singleton at application level you should define it in app.module.ts

    providers: [ MyApplicationService ] (you can define the same in child module as well to make it that module specific)

    • Do not add this service in provider which creates an instance for that component which breaks the singleton concept, just inject through constructor.
  2. If you want to define singleton service at component level create service, add that service in app.module.ts and add in providers array inside specific component as shown in below snipet.

    @Component({ selector: 'app-root', templateUrl: './test.component.html', styleUrls: ['./test.component.scss'], providers : [TestMyService] })

  3. Angular 6 provide new way to add service at application level. Instead of adding a service class to the providers[] array in AppModule , you can set the following config in @Injectable() :

    @Injectable({providedIn: 'root'}) export class MyService { ... }

The "new syntax" does offer one advantage though: Services can be loaded lazily by Angular (behind the scenes) and redundant code can be removed automatically. This can lead to a better performance and loading speed - though this really only kicks in for bigger services and apps in general.

Vanesavanessa answered 9/7, 2018 at 14:36 Comment(0)
S
0

In addition to the above excellent answers, there may be something else that is missing if things in your singleton still aren't behaving as a singleton. I ran into the issue when calling a public function on the singleton and finding that it was using the wrong variables. It turns out that the problem was the this isn't guaranteed to be bound to the singleton for any public functions in the singleton. This can be corrected by following the advice here, like so:

@Injectable({
  providedIn: 'root',
})
export class SubscriptableService {
  public serviceRequested: Subject<ServiceArgs>;
  public onServiceRequested$: Observable<ServiceArgs>;

  constructor() {
    this.serviceRequested = new Subject<ServiceArgs>();
    this.onServiceRequested$ = this.serviceRequested.asObservable();

    // save context so the singleton pattern is respected
    this.requestService = this.requestService.bind(this);
  }

  public requestService(arg: ServiceArgs) {
    this.serviceRequested.next(arg);
  }
}

Alternatively, you can simply declare the class members as public static instead of public, then the context won't matter, but you'll have to access them like SubscriptableService.onServiceRequested$ instead of using dependency injection and accessing them via this.subscriptableService.onServiceRequested$.

Splanchnology answered 2/12, 2018 at 18:30 Comment(0)
F
0

Parent and child services

I was having trouble with a parent service and its child using different instances. To force one instance to be used, you can alias the parent with reference to the child in your app module providers. The parent will not be able to access the child's properties, but the same instance will be used for both services. https://angular.io/guide/dependency-injection-providers#aliased-class-providers

app.module.ts

providers: [
  ChildService,
  // Alias ParentService w/ reference to ChildService
  { provide: ParentService, useExisting: ChildService}
]

Services used by components outside of your app modules scope

When creating a library consisting of a component and a service, I ran into an issue where two instances would be created. One by my Angular project and one by the component inside of my library. The fix:

my-outside.component.ts

@Component({...})
export class MyOutsideComponent {
  @Input() serviceInstance: MyOutsideService;
  ...
}

my-inside.component.ts

  constructor(public myService: MyOutsideService) { }

my-inside.component.hmtl

<app-my-outside [serviceInstance]="myService"></app-my-outside>
Fancher answered 26/12, 2019 at 21:37 Comment(1)
Did you mean to answer your own question? If so, you can separate out the answer into a formal Answer on StackOverflow, by cut/pasting it to the Answer field after the Question is posted.Allomerism
D
0

Well, Scope of Angular service depends upon where you provide the service at root module, lazy loaded module or at component level.

Here is a video which describe it beutifully with real examples.

https://youtu.be/aDyqnQrer3o

Disjoined answered 14/10, 2021 at 17:40 Comment(2)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Lass
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From ReviewEmanuele

© 2022 - 2024 — McMap. All rights reserved.