typescript - Cum pot să creez un serviciu singleton în Angular 2?
Am citit că injectarea în timpul bootstrap-ului ar trebui să facă ca toți copii să împartă aceeași instanță, dar componentele mele principale și header (aplicația principală include componenta header și router-outlet) primesc fiecare o instanță separată a serviciilor mele.
Am un FacebookService pe care îl folosesc pentru a face apeluri la API-ul JavaScript Facebook și un UserService care folosește FacebookService. Iată bootstrap-ul meu:
bootstrap(MainAppComponent, [ROUTER_PROVIDERS, UserService, FacebookService]);
Din jurnalizarea mea, se pare că apelul bootstrap se termină, apoi văd că se creează FacebookService și apoi UserService înainte ca codul din fiecare constructor să ruleze, MainAppComponent, HeaderComponent și DefaultComponent:
Răspuns #1
Actualizare (Angular 6 +)
Modul recomandat de a crea un serviciu singleton s-a schimbat. Acum se recomandă să specificați în decoratorul @Injectable al serviciului că acesta ar trebui să fie furnizat în 'root'. Aceasta are mult sens pentru mine și nu mai este nevoie să listați toate serviciile furnizate în modulele voastre deloc. Trebuie doar să importați serviciile atunci când aveți nevoie de ele și acestea se înregistrează singure în locul potrivit. De asemenea, puteți specifica un modul astfel încât acesta să fie furnizat doar dacă modulul este importat.
@Injectable({
providedIn: 'root',
})
export class ApiService {
}
- https://angular.io/guide/singleton-services
- https://angular.io/guide/providers
Actualizare (Angular 2)
Cu NgModule, modul de a face asta acum cred că este să creezi un 'CoreModule' cu clasa ta de serviciu în el și să listezi serviciul în providers ai modulului. Apoi imporți modulul core în modulul principal al aplicației tale, ceea ce va furniza o singură instanță pentru orice copii care solicită acea clasă în constructorii lor:
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 { }
Răspuns original
Dacă enumeri un furnizor în bootstrap(), nu trebuie să îl mai enumeri în decoratorul componentului tău:
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 fapt, listarea clasei tale în 'providers' creează o nouă instanță a acesteia; dacă orice componentă părinte deja o listează, atunci copiii nu trebuie să o facă, iar dacă o fac, vor primi o nouă instanță.
Răspunsul #2
Jason are completă dreptate! Este cauzat de modul în care funcționează injecția de dependență. Se bazează pe injectoare ierarhice.
Există mai mulți iniectori într-o aplicație Angular2:
- Contul root pe care îl configurezi când inițializezi aplicația ta
- Un injector pe componentă. Dacă folosești o componentă într-o alta. Injectorul componentei este un copil al injectorului componentei părinte. Componenta aplicației (cea pe care o specifici la inițializarea aplicației tale) are ca părinte injectorul rădăcină).
Când Angular2 încearcă să injecteze ceva în constructorul componentului:
- Se uită în injectorul asociat componentului. Dacă există unul corespunzător, îl va folosi pentru a obține instanța corespunzătoare. Această instanță este creată lazy și este un singleton pentru acest injector.
- Dacă nu există un furnizor la acest nivel, se va uita la injectorul părinte (și așa mai departe).
Deci, dacă vrei să ai un singleton pentru întreaga aplicație, trebuie să ai provider-ul definit fie la nivelul injectorului rădăcină, fie la nivelul injectorului componentului aplicației.
Dar Angular2 va privi arborele injectorului de jos în sus. Aceasta înseamnă că providerul de la cel mai jos nivel va fi utilizat și domeniul instanței asociate va fi acest nivel.
Vezi această întrebare pentru mai multe detalii:
- Care este cea mai bună metodă de a injecta un serviciu într-altul în Angular 2 (Beta)?
Răspunsul #3
Știu că Angular are injectoare ierarhice, așa cum a spus Thierry.
Dar am o altă opțiune aici în cazul în care găsești un caz de utilizare în care nu vrei cu adevărat să o injectezi la părinte.
Putem realiza acest lucru creând o instanță a serviciului și, la furnizare, să returnăm întotdeauna acea instanță.
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);
}
})
];
Și apoi, în componenta ta, folosești metoda ta personalizată de furnizare.
@Component({
providers: [YOUR_SERVICE_PROVIDER]
})
Și ar trebui să ai un serviciu singleton fără a depinde de injectoarele ierarhice.
Nu spun că aceasta este o metodă mai bună, este doar în caz că cineva are o problemă în care injectorii ierarhici nu sunt posibili.
Răspunsul #4
Sintaxa a fost schimbată. Verifică acest link
Dependențele sunt singletoni în cadrul domeniului unui injector. În exemplul de mai jos, o singură instanță HeroService este partajată între HeroesComponent și componentele sale copil HeroListComponent.
Pasul 1. Creați o clasă singleton cu decoratorul @Injectable
@Injectable()
export class HeroService {
getHeroes() { return HEROES; }
}
Pasul 2. Injecție în constructor
export class HeroListComponent {
constructor(heroService: HeroService) {
this.heroes = heroService.getHeroes();
}
Pasul 3. Înregistrați furnizorul
@NgModule({
imports: [
BrowserModule,
FormsModule,
routing,
HttpModule,
JsonpModule
],
declarations: [
AppComponent,
HeroesComponent,
routedComponents
],
providers: [
HeroService
],
bootstrap: [
AppComponent
]
})
export class AppModule { }
