So fügen Sie einen Service in die Klasse ein (keine Komponente)

90

Ich möchte einen Dienst in eine Klasse einfügen, die keine Komponente ist .

Zum Beispiel:

Myservice

import {Injectable} from '@angular/core';
@Injectable()
export class myService {
  dosomething() {
    // implementation
  }
}

Meine Klasse

import { myService } from './myService'
export class MyClass {
  constructor(private myservice:myService) {

  }
  test() {
     this.myservice.dosomething();
  }
}

Diese Lösung funktioniert nicht (ich denke, weil sie MyClassnoch nicht instanziiert wurde).

Gibt es eine andere Möglichkeit, einen Dienst in einer Klasse (nicht in einer Komponente) zu verwenden? Oder würden Sie mein Code-Design als unangemessen betrachten (um einen Dienst in einer Klasse zu verwenden, die keine Komponente ist)?

Vielen Dank.

Elec
quelle

Antworten:

56

Injections funktioniert nur mit Klassen, die durch Angulars Dependency Injection (DI) instanziiert werden.

  1. Du brauchst
    • @Injectable()zu MyClassund hinzufügen
    • bieten MyClasswie providers: [MyClass]in einer Komponente oder NgModule.

Wenn Sie dann MyClassirgendwo injizieren , wird eine MyServiceInstanz übergeben, MyClasswenn sie von DI instanziiert wird (bevor sie das erste Mal injiziert wird).

  1. Ein alternativer Ansatz besteht darin, einen benutzerdefinierten Injektor wie zu konfigurieren
constructor(private injector:Injector) { 
  let resolvedProviders = ReflectiveInjector.resolve([MyClass]);
  let childInjector = ReflectiveInjector.fromResolvedProviders(resolvedProviders, this.injector);

  let myClass : MyClass = childInjector.get(MyClass);
}

Dieser Weg myClassist eine MyClassInstanz, die von Angulars DI instanziiert wird und bei Instanziierung myServiceinjiziert wird MyClass.
Siehe auch Abhängigkeiten von Injector manuell innerhalb einer Direktive abrufen

  1. Eine weitere Möglichkeit besteht darin, die Instanz selbst zu erstellen:
constructor(ms:myService)
let myClass = new MyClass(ms);
Günter Zöchbauer
quelle
2
Ich denke, dass das Injizieren eines Dienstes für eine eigenständige Klasse ein Anti-Muster ist.
Toméxx
11
Sicher, aber manchmal kann es immer noch Sinn machen und es ist immer gut zu wissen, was möglich ist
Günter Zöchbauer
Warum einen neuen Injektor im Konstruktor erstellen? Warum nicht die injizierte verwenden? Wenn Sie eine neue erstellen wollen, gibt es keinen Grund, eine zu injizieren
smac89
Der neue ist ein Kinderinjektor mit zusätzlichen Anbietern. Wenn die dem untergeordneten Injektor hinzugefügten Anbieter von Anbietern abhängen, die auf übergeordneten Komponenten oder auf Modulebene bereitgestellt werden, kann DI alles automatisch auflösen. Daher gibt es definitiv Situationen, in denen dies sinnvoll ist.
Günter Zöchbauer
1
@ TimothyPenz danke für die Bearbeitung. In diesem Beispiel privateist das nicht erforderlich, da injectores nicht außerhalb des Konstruktors verwendet wird und daher keine Referenz in einem Feld gespeichert werden muss.
Günter Zöchbauer
29

Keine direkte Antwort auf die Frage, aber wenn Sie diese SO aus dem Grund lesen, aus dem ich bin, kann dies helfen ...

Angenommen , Sie verwenden ng2-translate und möchten wirklich, dass Ihre User.tsKlasse es hat. Du denkst sofort, du sollst DI verwenden, um es einzufügen, schließlich machst du Angular. Aber das ist eine Art Überdenken, Sie können es einfach in Ihrem Konstruktor übergeben oder es zu einer öffentlichen Variablen machen, die Sie von der Komponente festgelegt haben (in der Sie es vermutlich DI gemacht haben).

z.B:

import { TranslateService } from "ng2-translate";

export class User {
  public translateService: TranslateService; // will set from components.

  // a bunch of awesome User methods
}

dann von einer benutzerbezogenen Komponente, die TranslateService injiziert hat

addEmptyUser() {
  let emptyUser = new User("", "");
  emptyUser.translateService = this.translateService;
  this.users.push(emptyUser);
}

Hoffentlich hilft das denen da draußen wie mir, die viel schwieriger schreiben wollten, um Code zu pflegen, weil wir manchmal zu schlau sind =]

(HINWEIS: Der Grund, warum Sie möglicherweise eine Variable festlegen möchten, anstatt sie zu einem Teil Ihrer Konstruktormethode zu machen, besteht darin, dass Sie den Dienst möglicherweise nicht verwenden müssen. Wenn Sie ihn also immer übergeben müssen, müssen Sie zusätzliche Importe einführen / Code, der nie wirklich benutzt wird)

Ryan Crews
quelle
und vielleicht machen Sie translateServiceein get / set, wo das geteinen bedeutungsvollen Fehler anstelle einer NullReference-Ausnahme auslöst, wenn Sie vergessen haben, es zu setzen
Simon_Weaver
1
Vielleicht ist es sinnvoller, translateService zu einem erforderlichen Parameter im Klassenkonstruktor zu machen? Dieses Muster entspricht eher dem DI-Muster imho.
Icycool
16

Das ist irgendwie ( sehr ) hackig, aber ich dachte, ich würde auch meine Lösung teilen. Beachten Sie, dass dies nur mit Singleton-Diensten funktioniert (injiziert im App-Stammverzeichnis, nicht in der Komponente!), Da diese so lange wie Ihre Anwendung ausgeführt werden und es immer nur eine Instanz davon gibt.

Erstens in Ihrem Dienst:

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

    doSomething() {
        console.log("something!");
    }
}

Dann in jeder Klasse:

export class MyClass {
    constructor() {
        MyService.instance.doSomething();
    }
}

Diese Lösung ist gut, wenn Sie die Code-Unordnung reduzieren möchten und ohnehin keine Nicht-Singleton-Dienste verwenden.

Kilves
quelle
Aber wie verwendet man MyService in MyClass ohne Deklaration im Konstruktor?
Akhil V
@AkhilV Sie importieren einfach den MyService wie jede andere Klasse, dann können Sie die Methode wie beschrieben direkt aufrufen.
Kilves
13

locator.service.ts

import {Injector} from "@angular/core";

export class ServiceLocator {
    static injector: Injector;
}

app.module.ts

@NgModule({ ... })

export class AppModule {
    constructor(private injector: Injector) {
        ServiceLocator.injector = injector;
    }
 }

poney.model.ts

export class Poney {

    id: number;
    name: string;
    color: 'black' | 'white' | 'brown';

    service: PoneyService = ServiceLocator.injector.get(PoneyService); // <--- HERE !!!

    // PoneyService is @injectable and registered in app.module.ts
}
Julien
quelle
1
Ich bin mir nicht sicher, was die beste Vorgehensweise für das OP-Szenario ist, aber diese Antwort scheint so viel einfacher zu sein als die führenden. Funktioniert das Verschieben des injector.get()Aufrufs in das Modul (unter der Annahme eines zustandslosen Dienstes, damit mehrere Klassen dieselbe Instanz "gemeinsam nutzen" können)?
G0BLiN
Ich denke, dass Code von allen Poney-Instanzen erhalten würde, die dieselbe Instanz des Poney-Dienstes gemeinsam nutzen. Was denken Sie?
Julien
Julien - das ist ein akzeptables Ergebnis (eigentlich das bevorzugte) für mein Szenario - stellen Sie sich so etwas wie einen Eingabevalidierer, einen Benutzerberechtigungs-Handler oder einen Lokalisierungsdienst vor - bei dem Sie die gleiche Funktionalität für die gesamte App benötigen, jedoch nicht für jede Instanz eine eindeutige Instanz erforderlich ist einzelner Verbraucher der Dienstleistung.
G0BLiN
Probleme beim Versuch, dies in Jasmin-Tests zum Laufen zu bringen. Wie konfiguriere ich das Test-Setup? ServiceLocator.injector gibt weiterhin null zurück, obwohl ich den "PoneyService" in das TestBed injiziert habe?
GGizmos
3

Wenn Ihre Servicemethoden reine Funktionen sind, besteht eine saubere Möglichkeit, dies zu lösen, darin, statische Mitglieder in Ihrem Service zu haben.

dein Dienst

import {Injectable} from '@angular/core';
@Injectable()
export class myService{
  public static dosomething(){
    //implementation => doesn't use `this`
  }
}

deine Klasse

export class MyClass{
  test(){
     MyService.dosomething(); //no need to inject in constructor
  }
}
dasfdsa
quelle