web-dev-qa-db-fra.com

Comment créer un service singleton dans Angular 2?

J'ai lu que l'injection lors du démarrage devrait avoir tous les enfants partageant la même instance, mais mes composants principal et en-tête (l'application principale comprend le composant en-tête et le routeur-prise) reçoivent chacun une instance distincte de mes services. 

J'ai un service Facebook que j'utilise pour passer des appels à l'API javascript de Facebook et un service d'utilisateur qui utilise le service Facebook. Voici mon bootstrap:

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

À partir de ma journalisation, il semble que l'appel d'amorçage se termine, puis je vois le FacebookService, puis le UserService créé avant que le code de chacun des constructeurs ne s'exécute, les composants MainAppComponent, HeaderComponent et DefaultComponent

 enter image description here

121
Jason Goemaat

Jason a complètement raison! Cela est dû au fonctionnement de l'injection de dépendance. Il est basé sur des injecteurs hiérarchiques.

Il existe 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 dans un autre. L'injecteur de composant est un enfant du composant parent. Le composant d’application (celui que vous spécifiez lors du surampliage de votre application) a pour parent l’injecteur racine.

Quand Angular2 essaie d’injecter quelque chose dans le constructeur du composant:

  • Il examine l'injecteur associé au composant. S'il en existe un, il l'utilisera pour obtenir l'instance correspondante. Cette instance est créée paresseusement et constitue un singleton pour cet injecteur.
  • S'il n'y a pas de prestataire à ce niveau, il se penchera sur l'injecteur parent (et ainsi de suite).

Donc, si vous voulez avoir un singleton pour toute l'application, vous devez définir le fournisseur au niveau de l'injecteur racine ou de l'injecteur du composant d'application.

Mais Angular2 examinera l’arbre d’injection par le bas. Cela signifie que le fournisseur du niveau le plus bas sera utilisé et que la portée de l'instance associée correspond à ce niveau.

Voir cette question pour plus de détails:

117
Thierry Templier

Mise à jour (angulaire 6)

La méthode recommandée pour créer un service singleton a été modifiée. Il est maintenant recommandé de spécifier dans le décorateur @Injectable du service que celui-ci doit être fourni dans la 'racine'. Cela me semble tout à fait logique et il n’est plus nécessaire de répertorier tous les services fournis dans vos modules. Vous importez juste les services quand vous en avez besoin et ils s'enregistrent au bon endroit. Vous pouvez également spécifier un module afin qu'il ne soit fourni que si le module est importé.

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

Mise à jour (angulaire 2)

Avec NgModule, la meilleure façon de procéder consiste à créer un «CoreModule» contenant votre classe de services et à répertorier le service dans les fournisseurs du module. Ensuite, vous importez le module principal dans votre module principal d'application, qui fournira une 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 répertoriez un fournisseur dans bootstrap(), vous n'avez pas besoin de les répertorier dans votre décorateur de composants:

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' en crée une nouvelle instance. Si un composant parent la répertorie déjà, les enfants n'en ont pas besoin et, s'ils le font, ils obtiendront une nouvelle instance.

120
Jason Goemaat

Je sais que angular a des injecteurs hiérarchiques comme dit Thierry. 

Mais j’ai une autre option ici si vous trouvez 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 en le renvoyant toujours.

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 puis sur votre composant, vous utilisez votre méthode de fourniture personnalisée. 

@Component({
  providers: [YOUR_SERVICE_PROVIDER]
})

Et vous devriez avoir un service singleton sans dépendre des injecteurs hiérarchiques.

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

24
Joel Almeida

La syntaxe a été modifiée. Cochez cette link

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 HeroesComponent et ses enfants HeroListComponent.

Étape 1. Créez une classe singleton avec le décorateur @Injectable.

@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. Enregistrez le fournisseur

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

L'ajout de @Injectable decorator au service,ETl'enregistrer en tant que fournisseur dans le module racine en fera un singleton.

7
bresleveloper

cela semble bien fonctionner pour moi

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

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

Voici un exemple de travail avec la version 2.3 angulaire. Appelez simplement le constructeur du service de manière stand-by comme ce constructeur (private _userService: UserService). Et cela créera un singleton pour l'application. 

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

Dans Angular @ 6, vous pouvez avoir providedIn dans un Injectable

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

}

Vérifiez la docs ici

Il existe deux manières de transformer un service en singleton en Angular:

  1. Déclarez que le service doit être fourni dans la racine de l'application.
  2. Incluez le service dans le module d'application ou dans un module uniquement importé par le module d'application.

À partir de Angular 6.0, le moyen préféré pour créer un service singleton est de spécifier sur le service qu'il doit être fourni à la racine de l'application. Pour ce faire, configurez «includesIn» à root sur le décorateur @Injectable du service:

3
sabithpocker

Vous pouvez utiliser useValue dans les fournisseurs

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

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

Déclarez simplement votre service en tant que fournisseur dans app.module.ts uniquement. 

Cela a fait le travail pour moi.

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

puis instanciez-le à l'aide d'un paramètre privé du constructeur:

constructor(private topicService: TopicService) { }

ou puisque si votre service est utilisé à partir de HTML, l'option -prod réclamera:

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

ajoutez un membre pour votre service et remplissez-le avec l'instance reçue dans le constructeur:

export class SomeComponent {
  topicService: TopicService;
  constructor(private topicService: TopicService) { 
    this.topicService= topicService;
  }
}
2
user1767316
  1. Si vous voulez rendre le service singleton au niveau de l'application.__, vous devez le définir dans app.module.ts

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

    • N'ajoutez pas ce service au fournisseur qui crée une instance pour ce composant qui casse le concept de singleton, injectez simplement par le constructeur.
  2. Si vous souhaitez définir un service singleton au niveau du composant Create service, ajoutez ce service dans app.module.ts, puis ajoutez un tableau de fournisseurs dans un composant spécifique, comme indiqué dans le snipet ci-dessous.

    @Component ({ Sélecteur: 'app-root', TemplateUrl: './test.component.html' , StyleUrls: ['./test.component.scss' , fournisseurs: [TestMyService] })

  3. Angular 6 fournit un nouveau moyen d’ajouter un service au niveau application ..__ Au lieu d’ajouter une classe de service au tableau providers [] dans AppModule, vous pouvez définir la configuration suivante dans @Injectable ():

    @Injectable ({fournieIn: 'racine'}) Classe d'exportation MyService {...}

La "nouvelle syntaxe" offre toutefois un avantage: les services peuvent être chargés paresseusement par Angular (en coulisses) et le code redondant peut être supprimé automatiquement. Cela peut améliorer les performances et la vitesse de chargement, même si cela ne concerne que les services et les applications plus volumineux en général.

0
Vijay Barot

En plus des excellentes réponses ci-dessus, il se peut qu'il manque quelque chose si les éléments de votre singleton ne se comportent toujours pas comme un singleton. J'ai rencontré le problème lorsque j'ai appelé une fonction publique sur le singleton et découvert qu'elle utilisait les mauvaises variables. Il s'avère que le problème est que la variable this n'est pas garantie d'être liée au singleton pour des fonctions publiques dans le singleton. Ceci peut être corrigé en suivant le conseil ici , comme suit:

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

Sinon, vous pouvez simplement déclarer les membres de la classe en tant que public static au lieu de public, le contexte n'aura donc aucune importance, mais vous devrez y accéder comme SubscriptableService.onServiceRequested$ au lieu d'utiliser l'injection de dépendance et d'y accéder via this.subscriptableService.onServiceRequested$.

0
cjbarth