How do I create a singleton service in Angular 2?

对着背影说爱祢 提交于 2019-12-16 22:18:06

问题


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:


回答1:


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:

  • What's the best way to inject one service into another in angular 2 (Beta)?



回答2:


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 {
}
  • https://angular.io/guide/singleton-services
  • https://angular.io/guide/providers

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.




回答3:


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.




回答4:


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 { }



回答5:


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




回答6:


this seems to be working well for me

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

  constructor() {
    return MyStaticService.instance = MyStaticService.instance || this;
  }
}



回答7:


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



回答8:


You can use useValue in providers

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

@NgModule({
...
  providers: [ { provide: MyService, useValue: new MyService() } ],
...
})



回答9:


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:




回答10:


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



回答11:


  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.




回答12:


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$.



来源:https://stackoverflow.com/questions/36198785/how-do-i-create-a-singleton-service-in-angular-2

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!