Comment injecter un service dans une classe (pas un composant)

88

Je souhaite injecter un service dans une classe qui n'est pas un composant .

Par exemple:

Mon service

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

Ma classe

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

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

Cette solution ne fonctionne pas (je pense parce qu'elle MyClassn'a pas encore été instanciée).

Existe-t-il une autre façon d'utiliser un service dans une classe (pas un composant)? Ou considérez-vous ma conception de code comme inappropriée (pour utiliser un service dans une classe qui n'est pas un composant)?

Merci.

Elec
la source

Réponses:

56

Les injections ne fonctionnent qu'avec les classes instanciées par l'injection de dépendances Angulars (DI).

  1. Tu dois
    • ajouter @Injectable()à MyClasset
    • fournir MyClasscomme providers: [MyClass]dans un composant ou NgModule.

Lorsque vous injectez ensuite MyClassquelque part, une MyServiceinstance est transmise MyClasslorsqu'elle est instanciée par DI (avant d'être injectée la première fois).

  1. Une autre approche consiste à configurer un injecteur personnalisé comme
constructor(private injector:Injector) { 
  let resolvedProviders = ReflectiveInjector.resolve([MyClass]);
  let childInjector = ReflectiveInjector.fromResolvedProviders(resolvedProviders, this.injector);

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

Cette manière myClasssera une MyClassinstance, instanciée par Angulars DI, et myServicesera injectée une MyClassfois instanciée.
Voir aussi Obtenir manuellement la dépendance d'Injector dans une directive

  1. Une autre manière est de créer l'instance vous-même:
constructor(ms:myService)
let myClass = new MyClass(ms);
Günter Zöchbauer
la source
2
Je pense qu'injecter un service à une classe autonome est un anti-modèle.
tomexx
11
Bien sûr, mais parfois cela peut toujours avoir du sens et il est toujours bon de savoir ce qui est possible
Günter Zöchbauer
Pourquoi créer un nouvel injecteur dans le constructeur? Pourquoi ne pas utiliser celui qui a été injecté? Si vous envisagez d'en créer un nouveau, il n'y a aucune raison d'en faire injecter un en premier lieu
smac89
Le nouveau est un injecteur pour enfants avec des prestataires supplémentaires. Si les fournisseurs ajoutés à l'injecteur enfant dépendent des fournisseurs fournis au niveau des composants parents ou au niveau du module, alors DI peut tout résoudre automatiquement. Par conséquent, il existe certainement des situations où cela a du sens.
Günter Zöchbauer
1
@TimothyPenz merci pour la modification. Dans cet exemple, le privaten'est pas nécessaire, car il injectorn'est pas utilisé en dehors du constructeur, donc pas besoin de conserver une référence dans un champ.
Günter Zöchbauer
29

Pas une réponse directe à la question, mais si vous lisez ce SO pour la raison que je suis, cela peut aider ...

Disons que vous utilisez ng2-translate et que vous voulez vraiment que votre User.tsclasse l'ait. Vous pensez immédiatement utiliser DI pour le mettre, vous faites Angular après tout. Mais c'est un peu trop réfléchir, vous pouvez simplement le passer dans votre constructeur ou en faire une variable publique que vous avez définie à partir du composant (où vous l'avez probablement fait).

par exemple:

import { TranslateService } from "ng2-translate";

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

  // a bunch of awesome User methods
}

puis à partir d'un composant lié à l'utilisateur qui a injecté TranslateService

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

J'espère que cela aidera ceux qui, comme moi, étaient sur le point d'écrire du code beaucoup plus difficile à maintenir parce que nous sommes parfois trop intelligents =]

(REMARQUE: la raison pour laquelle vous voudrez peut-être définir une variable au lieu de l'inclure dans votre méthode de constructeur est que vous pourriez avoir des cas où vous n'avez pas besoin d'utiliser le service, donc toujours être obligé de le transmettre signifierait l'introduction d'importations supplémentaires / code qui ne sont jamais vraiment utilisés)

Ryan Crews
la source
et peut-être faire translateServiceun get / set où le getlance une erreur significative au lieu d'une exception NullReference si vous avez oublié de le définir
Simon_Weaver
1
Peut-être qu'il est plus logique de faire de translateService un paramètre obligatoire dans le constructeur de classe? Ce modèle est plus conforme au modèle DI à mon humble avis.
Icycool
15

C'est un peu ( très ) hacky, mais j'ai pensé partager ma solution aussi. Notez que cela ne fonctionnera qu'avec les services Singleton (injectés à la racine de l'application, pas dans le composant!), Car ils vivent aussi longtemps que votre application, et il n'y en a qu'une seule instance.

Tout d'abord, à votre service:

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

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

Puis dans n'importe quelle classe:

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

Cette solution est bonne si vous souhaitez réduire l'encombrement du code et n'utilisez pas de services non singleton de toute façon.

Kilves
la source
Mais comment utiliser MyService dans MyClass sans aucune déclaration dans le constructeur?
Akhil V
@AkhilV vous importez simplement MyService comme vous importeriez n'importe quelle autre classe, puis vous pouvez directement appeler la méthode comme décrit.
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
la source
1
Je ne sais pas quelle est la meilleure pratique pour le scénario d'OP, mais cette réponse semble tellement plus simple que les principales. Le déplacement de l' injector.get()appel vers le module fonctionnera-t-il (en supposant un service sans état afin que plusieurs classes puissent «partager» la même instance)?
G0BLiN
Je pense que ce code finirait par toutes les instances poney partageant la même instance de service poney. Qu'est-ce que tu penses?
Julien
Julien - c'est un résultat acceptable (en fait le résultat préféré) pour mon scénario - pensez à quelque chose comme un validateur d'entrée, un gestionnaire d'autorisations utilisateur ou un service de localisation - où vous avez besoin de la même fonctionnalité dans l'application, mais il n'est pas nécessaire d'avoir une instance unique pour chaque consommateur individuel du service.
G0BLiN
3

Si vos méthodes de service sont des fonctions pures, un moyen propre de résoudre ce problème consiste à avoir des membres statiques dans votre service.

ton service

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

ta classe

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