¿Cómo creo un servicio singleton en Angular 2?

He leído que se inyecta cuando bootstrapping debería hacer que todos los niños compartan la misma instancia, pero mis componentes principales y de encabezado (la aplicación principal incluye el componente de encabezado y el enrutador de salida) cada uno obtiene una instancia separada de mis servicios.

Tengo un FacebookService que utilizo para hacer llamadas a la API de Facebook y un UserService que usa FacebookService. Aquí está mi bootstrap:

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

Desde mi registro, parece que la llamada de arranque termina, luego veo que se crea el FacebookService y UserService antes de que se ejecute el código en cada uno de los constructores, MainAppComponent, HeaderComponent y DefaultComponent:

enter image description here

Jason tiene toda la razón! Es causado por la forma en que funciona la dependency injection. Está basado en inyectores jerárquicos.

Hay varios inyectores dentro de una aplicación Angular2:

  • La raíz que configura al iniciar su aplicación
  • Un inyector por componente. Si usas un componente dentro de otro. El inyector componente es un elemento secundario del componente primario. El componente de la aplicación (el que especifique cuando amplifica su aplicación) tiene el inyector raíz como principal).

Cuando Angular2 intenta inyectar algo en el constructor del componente:

  • Mira en el inyector asociado con el componente. Si hay uno coincidente, lo usará para obtener la instancia correspondiente. Esta instancia se crea perezosamente y es un singleton para este inyector.
  • Si no hay un proveedor en este nivel, mirará el inyector padre (y así sucesivamente).

Por lo tanto, si desea tener un singleton para toda la aplicación, necesita tener el proveedor definido ya sea en el nivel del inyector raíz o en el inyector de componentes de la aplicación.

Pero Angular2 mirará el árbol del inyector desde la parte inferior. Esto significa que se utilizará el proveedor en el nivel más bajo y el scope de la instancia asociada será este nivel.

Vea esta pregunta para más detalles:

  • ¿Cuál es la mejor manera de inyectar un servicio en otro en angular 2 (Beta)?

Actualización (Angular 6)

La forma recomendada de crear un servicio singleton ha cambiado. Ahora se recomienda especificar en el decorador @Injectable en el servicio que se debe proporcionar en la ‘raíz’. Esto tiene mucho sentido para mí y ya no es necesario enumerar todos los servicios provistos en sus módulos. Solo importa los servicios cuando los necesita y se registran en el lugar adecuado. También puede especificar un módulo para que solo se proporcione si el módulo se importa.

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

Actualización (Angular 2)

Con NgModule, la forma de hacerlo ahora es crear un ‘CoreModule’ con su clase de servicio y listar el servicio en los proveedores del módulo. A continuación, importe el módulo principal en su módulo de aplicación principal que proporcionará la instancia única a los niños que soliciten esa clase en sus constructores:

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

Respuesta original

Si incluye un proveedor en bootstrap() , no necesita enumerarlos en su decorador de componentes:

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

De hecho, al enumerar su clase en “proveedores” se crea una nueva instancia de la misma, si algún componente principal ya la incluye, los niños no necesitan, y si lo hacen, obtendrán una nueva instancia.

Sé que angular tiene inyectores jerárquicos, como dijo Thierry.

Pero tengo otra opción aquí en caso de que encuentre un caso de uso en el que realmente no desee inyectarlo en el padre.

Podemos lograrlo creando una instancia del servicio, y siempre proporcionamos eso.

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

Y luego en su componente utiliza su método de entrega personalizado.

 @Component({ providers: [YOUR_SERVICE_PROVIDER] }) 

Y debe tener un servicio único sin depender de los inyectores jerárquicos.

No digo que esta sea una mejor manera, es solo en caso de que alguien tenga un problema donde los inyectores jerárquicos no son posibles.

La syntax ha sido modificada. Ver este enlace

Las dependencias son singleton dentro del scope de un inyector. En el ejemplo siguiente, una sola instancia de HeroService se comparte entre HeroesComponent y sus hijos HeroListComponent.

Paso 1. Crea una clase singleton con @Injectable decorator

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

Paso 2. Inyectar en constructor

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

Paso 3. Registrar proveedor

 @NgModule({ imports: [ BrowserModule, FormsModule, routing, HttpModule, JsonpModule ], declarations: [ AppComponent, HeroesComponent, routedComponents ], providers: [ HeroService ], bootstrap: [ AppComponent ] }) export class AppModule { } 

Agregar el decorador @Injectable al Servicio, Y registrarlo como proveedor en el Módulo Raíz lo convertirá en un singleton.

Aquí hay un ejemplo de trabajo con la versión 2.3 de Angular. Simplemente llame al constructor del servicio como un constructor (private _userService: UserService). Y creará un singleton para la aplicación.

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

esto parece estar funcionando bien para mí

 @Injectable() export class MyStaticService { static instance: MyStaticService; constructor() { return MyStaticService.instance = MyStaticService.instance || this; } } 

Puede usar useValue en proveedores

 import { MyService } from './my.service'; @NgModule({ ... providers: [ { provide: MyService, useValue: new MyService() } ], ... }) 

Desde Angular @ 6, puede haber providedIn en un Injectable .

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

Verifique los documentos aquí

Hay dos formas de hacer un servicio singleton en Angular:

  1. Declare que el servicio debe proporcionarse en la raíz de la aplicación.
  2. Incluya el servicio en el AppModule o en un módulo que solo es importado por el AppModule.

A partir de Angular 6.0, la forma preferida de crear servicios únicos consiste en especificar en el servicio que se debe proporcionar en la raíz de la aplicación. Esto se hace estableciendo providedIn para rootear el @Injectable Decorator del servicio:

Simplemente declare su servicio como proveedor solo en app.module.ts.

Hizo el trabajo por mí.

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

entonces, ya sea instanciarlo usando un parámetro privado constructor:

 constructor(private topicService: TopicService) { } 

o dado que si su servicio se usa desde html, la opción -prod reclamará:

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

agregue un miembro para su servicio y llénelo con la instancia recibida en el constructor:

 export class SomeComponent { topicService: TopicService; constructor(private topicService: TopicService) { this.topicService= topicService; } } 
  1. Si desea hacer el servicio singleton a nivel de aplicación, debe definirlo en app.module.ts

    proveedores: [MyApplicationService] (también puede definir el mismo módulo hijo para que sea específico del módulo)

    • No agregue este servicio en el proveedor, que crea una instancia para ese componente que rompe el concepto de singleton, simplemente inyecte a través del constructor.
  2. Si desea definir el servicio singleton en el servicio de creación de nivel de componente, agregue ese servicio en app.module.ts y agregue la matriz de proveedores dentro del componente específico como se muestra en snipet a continuación.

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

  3. Angular 6 proporciona una nueva forma de agregar servicio a nivel de aplicación. En lugar de agregar una clase de servicio a la matriz de proveedores [] en AppModule, puede establecer la siguiente configuración en @Injectable ():

    @Injectable ({providedIn: ‘root’}) export class MyService {…}

Sin embargo, la “nueva syntax” ofrece una ventaja: Angular (detrás de escena) puede cargar los servicios de forma perezosa y el código redundante puede eliminarse automáticamente. Esto puede conducir a un mejor rendimiento y velocidad de carga, aunque esto realmente solo se aplica para servicios y aplicaciones más grandes en general.