Comment créer un service singleton dans Angular 2?

j'ai lu que l'injection quand bootstrapping devrait avoir tous les enfants partagent la même instance, mais mes Composants principaux et d'en-tête (l'application principale inclut le composant d'en-tête et routeur-sortie) reçoivent chacun une instance distincte de mes services.

j'ai un FacebookService que j'utilise pour faire des appels vers l'api JavaScript facebook et un UserService qui utilise le FacebookService. Voici mon bootstrap:

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

De mon enregistrement on dirait que l'appel bootstrap se termine, puis je vois le FacebookService puis UserService être créé avant le code dans chacun des constructeurs exécute, le MainAppComponent, le HeaderComponent et le DefaultComponent:

enter image description here

113
demandé sur smac89 2016-03-24 14:10:59

11 réponses

Jason a tout à fait raison! Il est causé par la façon dont l'injection de dépendance fonctionne. C'est basé sur des injecteurs hiérarchiques.

il y a plusieurs injecteurs dans une application Angular2:

  • la racine que vous configurez lorsque vous démarrez votre application
  • Un injecteur par composant. Si vous utilisez un composant à l'intérieur d'un autre. Le composant de l'injecteur est un enfant du composant parent. Application component (celui que vous spécifiez quand boostrapping votre application) a l'injecteur de racine comme parent un).

quand Angular2 tente d'injecter quelque chose dans le constructeur du composant:

  • il examine l'injecteur associé au composant. Si il y a correspondance, il va l'utiliser pour obtenir l'instance correspondante. Cette instance est créée paresseusement et est un singleton pour cet injecteur.
  • S'il n'y a pas fournisseur à ce niveau, il examinera l'injecteur parent (et ainsi de suite).

ainsi, si vous voulez avoir un unempleton pour l'ensemble de l'application, vous devez avoir le fournisseur défini soit au niveau de l'injecteur de racine ou de l'injecteur de Composant d'application.

mais Angular2 regardera l'arbre de l'injecteur du bas. Cela signifie que le fournisseur au niveau le plus bas sera utilisé et la portée de l'instance associée à ce niveau.

voir cette question pour plus de détails:

113
répondu Thierry Templier 2017-05-23 11:55:03

Mise À Jour (Angulaire 6)

la méthode recommandée pour créer un singleton service a changé. Il est maintenant recommandé de spécifier dans le décorateur @Injectable sur le service qu'il doit être fourni dans la "racine". Cela a beaucoup de sens pour moi et il n'est plus nécessaire d'énumérer tous les services fournis dans vos modules. Vous importez simplement les services lorsque vous en avez besoin et ils s'enregistrent à l'endroit approprié. Vous pouvez aussi spécifier un module ainsi il ne sera fourni que si le module est importé.

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

Mise À Jour (Angulaire 2)

avec NgModule, la façon de le faire maintenant je pense est de créer un' CoreModule ' avec votre classe de service dans la TI, et énumérer le service dans les fournisseurs du module. Ensuite, vous importez le module core dans votre module app principal qui fournira la seule instance à tous les enfants demandant cette classe dans leurs constructeurs:

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éponse Originale

si vous énumérez un fournisseur dans bootstrap() , vous n'avez pas besoin de les énumérer dans votre composant décorateur:

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

en fait la liste de votre classe dans 'providers' crée une nouvelle instance de celui-ci, si un composant parent l'a déjà listée, alors les enfants n'ont pas besoin de le faire, et s'ils le font, ils obtiendront une nouvelle instance.

115
répondu Jason Goemaat 2018-08-26 23:39:55

je sais qu'angular a des injecteurs hiérarchiques comme Thierry l'a dit.

mais j'ai une autre option ici au cas où vous trouveriez un cas d'utilisation où vous ne voulez pas vraiment l'injecter chez le parent.

nous pouvons y parvenir en créant une instance du service, et sur provide toujours retourner cela.

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

et ensuite sur votre composant vous utilisez votre méthode de fourniture personnalisée.

@Component({
  providers: [YOUR_SERVICE_PROVIDER]
})

et vous devrait avoir un service unique sans dépendre des injecteurs hiérarchiques.

Je ne dis pas que c'est une meilleure façon, est juste au cas où quelqu'un a un problème où les injecteurs hiérarchiques ne sont pas possibles.

24
répondu Joel Almeida 2017-11-30 09:38:40
La syntaxe

a été modifiée. Vérifier ce lien

Les dépendances

sont des singletons dans le cadre d'un injecteur. Dans l'exemple ci-dessous, une seule instance HeroService est partagée entre le HeroesComponent et ses enfants HeroListComponent.

Étape 1. Créer la classe singleton avec @injection decorator

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

Étape 2. Injecter dans le constructeur

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

Étape 3. Fournisseur du registre

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    routing,
    HttpModule,
    JsonpModule
  ],
  declarations: [
    AppComponent,
    HeroesComponent,
    routedComponents
  ],
  providers: [
    HeroService
  ],
  bootstrap: [
    AppComponent
  ]
})
export class AppModule { }
16
répondu Manish Jain 2017-01-27 15:39:52

ajouter @Injectable décorateur au Service, et l'enregistrer comme un fournisseur dans le Module racine en fera un singleton.

7
répondu bresleveloper 2017-10-02 11:45:53

voici un exemple de travail avec la version angulaire 2.3. Il suffit d'appeler le constructeur du service le stand way comme ce constructeur(privé _userService:UserService) . Et il va créer un singleton pour l'application.

de l'utilisateur.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.composant.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");
        });
    }
    ...
}
5
répondu David Dehghan 2017-01-12 07:06:02

cela semble bien fonctionner pour moi

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

  constructor() {
    return MyStaticService.instance = MyStaticService.instance || this;
  }
}
4
répondu Captain Harlock 2016-07-31 20:17:26

vous pouvez utiliser useValue dans les fournisseurs

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

@NgModule({
...
  providers: [ { provide: MyService, useValue: new MyService() } ],
...
})
4
répondu Roman Rhrn Nesterov 2017-01-22 20:50:48

Angulaires@6, vous pouvez avoir providedIn dans un Injectable .

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

}

cocher la Case docs ici

il y a deux façons de faire D'un service un simple en angle:

  1. déclare que le service doit être fourni dans l'application root.
  2. Inclure le service dans le AppModule ou dans un module qui n'est importée par l' AppModule.

commençant par Angular 6.0, la meilleure façon de créer un service singleton est de spécifier sur le service qu'il doit être fourni dans l'application root. Pour ce faire, il suffit de régler providedIn à root sur le décorateur @Injectable du service:

3
répondu sabithpocker 2018-05-10 08:53:03

il suffit de déclarer votre service en tant que fournisseur dans app.module.ts seulement.

Il a fait le travail pour moi.

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

puis soit l'instancier en utilisant un paramètre Privé du constructeur:

constructor(private topicService: TopicService) { }

ou depuis si votre service est utilisé à partir de html, l'option-prod demandera:

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

ajouter un membre pour votre service et le remplir avec l'instance reçue dans le constructeur:

export class SomeComponent {
  topicService: TopicService;
  constructor(private topicService: TopicService) { 
    this.topicService= topicService;
  }
}
2
répondu user1767316 2018-04-05 08:50:54
  1. vous devez le définir dans app.module.ts

    fournisseurs de: [ MyApplicationService ] (vous pouvez définir la même chose dans le module enfant aussi bien pour rendre ce module spécifique)

    • ne pas ajouter ce service dans provider qui crée une instance pour ce composant qui casse le concept singleton, il suffit d'injecter constructeur.
  2. si vous voulez définir le service singleton au niveau des composants créer le service, ajouter ce service via l'application.module.ts et add in providers forment un tableau à l'intérieur d'un composant spécifique comme indiqué dans snipet ci-dessous.

    @composant({ selecteur: 'app-root', templateUrl:"./test.composante.html', styleUrls: ["./test.composant.scss'], les fournisseurs de : [TestMyService] })

  3. Angulaire 6 offrent une nouvelle façon d'ajouter un service au niveau de l'application. Au lieu d'ajouter une classe de service au tableau providers[] dans AppModule, vous pouvez définir la configuration suivante dans @Injectable ():

    }) Classe export MyService { ... }

La "nouvelle syntaxe" offre un avantage cependant: les Services peuvent être chargés paresseusement par Angulaire (en coulisses) et code redondant peut être automatiquement supprimée. Cela peut conduire à une meilleure performance et à une vitesse de chargement plus rapide - bien que cela ne fasse que commencer pour les services et les applications plus importantes en général.

0
répondu Vijay Barot 2018-07-09 14:36:07