web-dev-qa-db-ger.com

Wie erstelle ich einen Singleton-Service in Angular 2?

Ich habe gelesen, dass das Injizieren beim Bootstrapping dazu führen sollte, dass alle Kinder dieselbe Instanz haben, aber meine Haupt- und Header-Komponenten (die Haupt-App enthält die Header-Komponente und den Router-Outlet) und jeweils eine separate Instanz meiner Services. 

Ich habe einen FacebookService, mit dem ich die Javascript-API von Facebook anrufe, und einen UserService, der den FacebookService verwendet. Hier ist mein Bootstrap:

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

Nach meiner Protokollierung sieht es so aus, als wäre der Bootstrap-Aufruf beendet. Dann sehe ich, dass der FacebookService und der UserService erstellt werden, bevor der Code in jedem der Konstruktoren ausgeführt wird. MainAppComponent, HeaderComponent und DefaultComponent:

 enter image description here

121
Jason Goemaat

Jason hat vollkommen recht! Es wird durch die Funktionsweise der Abhängigkeitsinjektion verursacht. Es basiert auf hierarchischen Injektoren.

Es gibt mehrere Injektoren in einer Angular2-Anwendung:

  • Das Root-Verzeichnis, das Sie beim Bootstrapping Ihrer Anwendung konfigurieren
  • Ein Injektor pro Komponente. Wenn Sie eine Komponente in einer anderen verwenden. Der Komponenteninjektor ist ein Kind der Elternkomponente. Die Anwendungskomponente (die Sie angeben, wenn Sie Ihre Anwendung verstärken) hat den Stamminjektor als übergeordneten.

Wenn Angular2 versucht, etwas in den Komponentenkonstruktor einzufügen:

  • Es untersucht den Injektor, der der Komponente zugeordnet ist. Wenn es eine Übereinstimmung gibt, wird es verwendet, um die entsprechende Instanz abzurufen. Diese Instanz ist träge angelegt und ist ein Singleton für diesen Injektor.
  • Wenn auf dieser Ebene kein Anbieter vorhanden ist, wird der übergeordnete Injektor (usw.) untersucht.

Wenn Sie also ein Singleton für die gesamte Anwendung erstellen möchten, muss der Anbieter entweder auf der Ebene des Root-Injektors oder des Injektors der Anwendungskomponente definiert sein.

Aber Angular2 betrachtet den Injektorbaum von unten. Dies bedeutet, dass der Provider auf der niedrigsten Ebene verwendet wird und der Umfang der zugeordneten Instanz diese Ebene sein wird.

Weitere Informationen finden Sie in dieser Frage:

117

Update (Winkel 6)

Die empfohlene Methode zum Erstellen eines Singleton-Dienstes hat sich geändert. Es wird jetzt empfohlen, im @Injectable-Dekorator für den Dienst anzugeben, dass er im 'root' bereitgestellt werden soll. Das ist für mich sehr sinnvoll und es ist nicht mehr nötig, alle angebotenen Dienste in Ihren Modulen aufzulisten. Sie importieren nur die Dienste, wenn Sie sie benötigen, und sie registrieren sich an der richtigen Stelle. Sie können auch ein Modul angeben so dass es nur bereitgestellt wird, wenn das Modul importiert wird.

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

Update (Winkel 2)

Ich denke, mit NgModule können Sie jetzt ein "CoreModule" erstellen, in dem Ihre Serviceklasse enthalten ist, und den Service in den Modulanbietern auflisten. Anschließend importieren Sie das Kernmodul in Ihr Haupt-App-Modul, das allen untergeordneten Elementen, die diese Klasse anfordern, eine Instanz in ihren Konstruktoren bereitstellt:

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

Ursprüngliche Antwort

Wenn Sie einen Anbieter in bootstrap() angeben, müssen Sie ihn nicht in Ihrem Komponentendekorateur auflisten:

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

Durch das Auflisten Ihrer Klasse in "provider" wird eine neue Instanz davon erstellt. Wenn eine übergeordnete Komponente diese bereits auflistet, müssen die untergeordneten Elemente dies nicht tun, und wenn dies der Fall ist, erhalten sie eine neue Instanz.

120
Jason Goemaat

Ich weiß, dass Angular hierarchische Injektoren hat, wie Thierry sagte. 

Aber ich habe hier eine andere Möglichkeit, falls Sie einen Anwendungsfall finden, bei dem Sie ihn nicht wirklich beim Elternteil injizieren möchten.

Wir können das erreichen, indem wir eine Instanz des Dienstes erstellen und diese immer zurückgeben.

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

Und dann verwenden Sie für Ihre Komponente Ihre benutzerdefinierte Bereitstellungsmethode. 

@Component({
  providers: [YOUR_SERVICE_PROVIDER]
})

Und Sie sollten einen Singleton-Dienst haben, ohne von den hierarchischen Injektoren abhängig zu sein.

Ich sage nicht, dass dies ein besserer Weg ist, nur wenn jemand ein Problem hat, bei dem hierarchische Injektoren nicht möglich sind.

24
Joel Almeida

Die Syntax wurde geändert. Überprüfen Sie diesen Link

Abhängigkeiten sind Singletons im Bereich eines Injektors. Im folgenden Beispiel wird eine einzelne HeroService-Instanz von der HeroesComponent und ihren HeroListComponent-Kindern gemeinsam genutzt.

Schritt 1. Erstellen Sie eine Singleton-Klasse mit dem @Injectable-Dekorator

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

Schritt 2. Injector injizieren

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

Schritt 3. Anbieter registrieren

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

Durch Hinzufügen von @Injectable decorator zum Dienst,UNDder Registrierung als Anbieter im Stammmodul wird es zu einem Singleton.

7
bresleveloper

das scheint gut für mich zu funktionieren

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

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

Hier ist ein Arbeitsbeispiel mit Angular Version 2.3. Rufen Sie den Konstruktor des Dienstes einfach wie diesen Konstruktor (private _userService: UserService) auf. Und es wird ein Singleton für die App erstellt. 

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

In Angular @ 6 können Sie providedIn in einer Injectable haben. 

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

}

Überprüfen Sie die Dokumente hier

Es gibt zwei Möglichkeiten, um einen Service in Angular zu einem Singleton zu machen:

  1. Erklären Sie, dass der Dienst im Anwendungsstamm bereitgestellt werden soll.
  2. Binden Sie den Dienst in das AppModule oder in ein Modul ein, das nur vom AppModule importiert wird.

Beginnend mit Angular 6.0 besteht die bevorzugte Methode zum Erstellen von Singleton-Diensten darin, dem Dienst anzugeben, dass er im Anwendungsstamm bereitgestellt werden soll. Dies wird durch das Festlegen von providedIn auf root im @Injectable-Dekorator des Dienstes erreicht:

3
sabithpocker

Sie können useValue in Providern verwenden

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

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

Geben Sie Ihren Dienst nur in app.module.ts als Anbieter an. 

Es hat die Arbeit für mich erledigt.

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

dann instanziieren Sie es mit einem privaten Parameter des Konstruktors:

constructor(private topicService: TopicService) { }

oder da der Dienst von html verwendet wird, beansprucht die Option -prod Folgendes:

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

fügen Sie ein Mitglied für Ihren Dienst hinzu und füllen Sie es mit der im Konstruktor erhaltenen Instanz:

export class SomeComponent {
  topicService: TopicService;
  constructor(private topicService: TopicService) { 
    this.topicService= topicService;
  }
}
2
user1767316
  1. Wenn Sie Service-Singleton auf Anwendungsebene erstellen möchten, müssen Sie es in app.module.ts

    anbieter: [ MyApplicationService ] (Sie können dasselbe auch im untergeordneten Modul definieren, um es für dieses Modul spezifisch zu machen.)

    • Fügen Sie diesen Dienst nicht in einem Provider hinzu, der eine Instanz für diese Komponente erstellt, die das Singleton-Konzept unterbricht. Fügen Sie einfach den Konstruktor ein.
  2. Wenn Sie den Singleton-Service auf Komponentenebene definieren möchten Create service, fügen Sie diesen Service in app.module.ts hinzu und fügen Sie in das Provider-Array eine bestimmte Komponente ein, wie im folgenden Snipet gezeigt.

    @Component ({ Selector: 'app-root', TemplateUrl: './test.component.html' , StyleUrls: ['./test.component.scss'(, Provider: [TestMyService] })

  3. Angular 6 bietet eine neue Möglichkeit zum Hinzufügen von Diensten auf Anwendungsebene. Anstatt dem Array [[]] [Provider] in AppModule eine Dienstklasse hinzuzufügen, können Sie die folgende Konfiguration in @Injectable () festlegen:

    @Injectable ({providedIn: 'root'}) Export class MyService {...}

Die "neue Syntax" bietet jedoch einen Vorteil: Dienste können von Angular (hinter den Kulissen) träge geladen und redundanter Code automatisch entfernt werden. Dies kann zu einer besseren Leistung und Ladegeschwindigkeit führen - obwohl dies wirklich nur bei größeren Services und Apps im Allgemeinen Einzug hält.

0
Vijay Barot

Zusätzlich zu den oben genannten hervorragenden Antworten kann noch etwas fehlen, wenn sich die Dinge in Ihrem Singleton immer noch nicht als Singleton verhalten. Ich bin auf das Problem gestoßen, als ich eine öffentliche Funktion für den Singleton aufrief und feststellte, dass er die falschen Variablen verwendete. Es stellt sich heraus, dass das Problem darin bestand, dass this nicht für alle öffentlichen Funktionen im Singleton an den Singleton gebunden ist. Dies kann durch Befolgen der Hinweise hier wie folgt korrigiert werden:

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

Alternativ können Sie die Klassenmitglieder einfach als public static anstelle von public deklarieren. Dann spielt der Kontext keine Rolle. Sie müssen jedoch wie SubscriptableService.onServiceRequested$ auf sie zugreifen, anstatt die Abhängigkeitseingabe zu verwenden und über this.subscriptableService.onServiceRequested$ zuzugreifen.

0
cjbarth