Jak wstrzyknąć usługę do klasy (nie komponentu)

88

Chcę wstrzyknąć usługę do klasy, która nie jest składnikiem .

Na przykład:

Myservice

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

Moja klasa

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

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

To rozwiązanie nie działa (myślę, że MyClassnie zostało jeszcze utworzone).

Czy istnieje inny sposób używania usługi w klasie (nie komponentu)? A może uważasz, że mój projekt kodu jest nieodpowiedni (aby używać usługi w klasie, która nie jest komponentem)?

Dziękuję Ci.

Elec
źródło

Odpowiedzi:

56

Injections działa tylko z klasami, których wystąpienie jest tworzone przez iniekcję zależności Angulars (DI).

  1. Musisz
    • dodaj @Injectable()do MyClassi
    • zapewniają MyClassjak providers: [MyClass]w komponencie lub NgModule.

Kiedy następnie MyClassgdzieś wstrzykujesz , MyServiceinstancja jest przekazywana do, MyClassgdy jest tworzona przez DI (zanim zostanie wstrzyknięta po raz pierwszy).

  1. Alternatywnym podejściem jest skonfigurowanie niestandardowego wtryskiwacza, takiego jak
constructor(private injector:Injector) { 
  let resolvedProviders = ReflectiveInjector.resolve([MyClass]);
  let childInjector = ReflectiveInjector.fromResolvedProviders(resolvedProviders, this.injector);

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

W ten sposób myClassbędzie MyClassinstancją utworzoną przez Angulars DI i myServicezostanie wstrzyknięta MyClasspo utworzeniu.
Zobacz także Ręczne pobieranie zależności od wtryskiwacza wewnątrz dyrektywy

  1. Jeszcze innym sposobem jest samodzielne utworzenie instancji:
constructor(ms:myService)
let myClass = new MyClass(ms);
Günter Zöchbauer
źródło
2
Myślę, że wstrzyknięcie usługi do samodzielnej klasy jest anty-wzorcem.
tomexx
11
Jasne, ale czasami może to mieć sens i zawsze dobrze jest wiedzieć, co jest możliwe
Günter Zöchbauer
Po co tworzyć nowy wtryskiwacz w konstruktorze? Dlaczego nie użyć tego, który został wstrzyknięty? Jeśli masz zamiar utworzyć nowy, nie ma powodu, aby mieć wstrzyknięty w pierwszej kolejności
smac89
Nowością jest wstrzykiwacz dziecięcy z dodatkowymi dostawcami. Jeśli dostawcy dodani do wtryskiwacza podrzędnego zależą od dostawców dostarczonych w składnikach nadrzędnych lub na poziomie modułu, funkcja DI może rozwiązać to wszystko automatycznie. Dlatego na pewno są sytuacje, w których ma to sens.
Günter Zöchbauer
1
@TimothyPenz dzięki za edycję. W tym przykładzie privatenie jest konieczne, ponieważ injectornie jest używane poza konstruktorem, dlatego nie ma potrzeby utrzymywania odwołania w polu.
Günter Zöchbauer
29

Nie jest to bezpośrednia odpowiedź na pytanie, ale jeśli czytasz to SO z powodu, dla którego jestem, może to pomóc ...

Powiedzmy, że używasz ng2-translate i naprawdę chcesz, aby Twoja User.tsklasa to miała. Od razu myślisz, że użyjesz DI, aby go umieścić, w końcu robisz Angular. Ale to trochę przesadne myślenie, możesz po prostu przekazać to w swoim konstruktorze lub uczynić z niego zmienną publiczną, którą ustawiłeś z komponentu (gdzie prawdopodobnie to zrobiłeś DI).

na przykład:

import { TranslateService } from "ng2-translate";

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

  // a bunch of awesome User methods
}

następnie z jakiegoś komponentu związanego z użytkownikiem, który wstrzyknął TranslateService

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

Mam nadzieję, że pomoże to tym, którzy są tacy jak ja, którzy mieli napisać dużo trudniejszy w utrzymaniu kod, ponieważ czasami jesteśmy zbyt sprytni =]

(UWAGA: powodem, dla którego możesz chcieć ustawić zmienną zamiast uczynić ją częścią swojej metody konstruktora, jest to, że możesz mieć przypadki, w których nie musisz używać usługi, więc zawsze konieczność jej przekazania oznaczałaby wprowadzenie dodatkowego importu / kod, który nigdy nie jest używany)

Ryan Crews
źródło
i może zrobić translateServiceget / set, w którym getrzuca znaczący błąd zamiast wyjątku NullReference, jeśli zapomniałeś go ustawić
Simon_Weaver
1
Może bardziej sensowne jest ustawienie translateService jako wymaganego parametru w konstruktorze klasy? Ten wzór jest bardziej zgodny z wzorcem DI imho.
Icycool
15

To trochę ( bardzo ) hackerskie, ale pomyślałem, że podzielę się również moim rozwiązaniem. Zwróć uwagę, że zadziała to tylko z usługami Singleton (wstrzykniętymi w katalogu głównym aplikacji, a nie komponentem!), Ponieważ działają one tak długo, jak Twoja aplikacja i istnieje tylko jedna ich instancja.

Po pierwsze, w twojej służbie:

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

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

Następnie w dowolnej klasie:

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

To rozwiązanie jest dobre, jeśli chcesz zmniejszyć bałagan w kodzie i i tak nie korzystasz z usług innych niż pojedyncze.

Kilves
źródło
Ale jak używać MyService w MyClass bez żadnej deklaracji w konstruktorze?
Akhil V
@AkhilV po prostu zaimportuj MyService tak, jak każdą inną klasę, a następnie możesz bezpośrednio wywołać metodę zgodnie z opisem.
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
źródło
1
Nie jestem pewien, jaka jest najlepsza praktyka w scenariuszu OP, ale ta odpowiedź wydaje się o wiele prostsza niż te wiodące. Czy przeniesienie injector.get()wywołania do modułu zadziała (przy założeniu, że usługa bezstanowa jest taka, że ​​kilka klas może „współużytkować” to samo wystąpienie)?
G0BLiN
Myślę, że ten kod trafiłby do wszystkich instancji poney, które współużytkują tę samą instancję usługi poney. Co myślisz?
Julien
Julien - to akceptowalny wynik (właściwie preferowany) dla mojego scenariusza - pomyśl o czymś takim jak walidator danych wejściowych, moduł obsługi uprawnień użytkownika lub usługa lokalizacji - gdzie potrzebujesz tej samej funkcjonalności w całej aplikacji, ale nie ma potrzeby tworzenia unikalnej instancji dla każdego z nich indywidualny konsument usługi.
G0BLiN
3

Jeśli metody usługi są czystymi funkcjami, prostym sposobem rozwiązania tego problemu jest posiadanie statycznych elementów członkowskich w usłudze.

Twoja usługa

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

Twoja klasa

export class MyClass{
  test(){
     MyService.dosomething(); //no need to inject in constructor
  }
}
dasfdsa
źródło