Come inserire il servizio nella classe (non nel componente)


91

Voglio iniettare un servizio in una classe che non è un componente .

Per esempio:

Myservice

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

La mia classe

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

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

Questa soluzione non funziona (penso perché MyClassnon è stata ancora istanziata).

C'è un altro modo per utilizzare un servizio in una classe (non un componente)? O considereresti il ​​mio progetto di codice inappropriato (per utilizzare un servizio in una classe che non è un componente)?

Grazie.

Risposte:


57

Injections funziona solo con classi istanziate da Angulars dependency injection (DI).

  1. Devi
    • aggiungi @Injectable()a MyClasse
    • fornire MyClasscome providers: [MyClass]in un componente o NgModule.

Quando poi si inietta MyClassda qualche parte, MyServiceun'istanza viene passata a MyClassquando viene istanziata da DI (prima che venga iniettata la prima volta).

  1. Un approccio alternativo consiste nel configurare un iniettore personalizzato come
constructor(private injector:Injector) { 
  let resolvedProviders = ReflectiveInjector.resolve([MyClass]);
  let childInjector = ReflectiveInjector.fromResolvedProviders(resolvedProviders, this.injector);

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

In questo modo myClasssarà MyClassun'istanza, istanziata da Angulars DI, e myServiceverrà iniettata MyClassquando istanziata.
Vedi anche Ottenere la dipendenza da Injector manualmente all'interno di una direttiva

  1. Un altro modo è creare tu stesso l'istanza:
constructor(ms:myService)
let myClass = new MyClass(ms);

2
Penso che l'inserimento del servizio in una classe autonoma sia un anti-pattern.
tomexx

11
Certo, ma a volte può ancora avere senso ed è sempre bene sapere cosa è possibile fare
Günter Zöchbauer

Perché creare un nuovo iniettore nel costruttore? Perché non usare quello che è stato iniettato? Se hai intenzione di crearne uno nuovo, non c'è motivo di averne uno iniettato in primo luogo
smac89

Il nuovo è un iniettore per bambini con fornitori aggiuntivi. Se i fornitori aggiunti all'iniettore figlio dipendono dai fornitori forniti a livello di componenti principali oa livello di modulo, DI può risolvere tutto automaticamente. Quindi ci sono sicuramente situazioni in cui questo ha senso.
Günter Zöchbauer

1
@TimothyPenz grazie per la modifica. In questo esempio il privatenon è necessario, perché injectornon è utilizzato al di fuori del costruttore, quindi non è necessario mantenere un riferimento in un campo.
Günter Zöchbauer

30

Non una risposta diretta alla domanda, ma se stai leggendo questo SO per il motivo per cui sono questo potrebbe aiutarti ...

Diciamo che stai usando ng2-translate e vuoi davvero che la tua User.tsclasse lo abbia. Il tuo pensiero immediato è di usare DI per inserirlo, dopotutto stai facendo Angular. Ma questo è un po 'pensarci troppo, puoi semplicemente passarlo nel tuo costruttore, o renderlo una variabile pubblica che hai impostato dal componente (dove presumibilmente l'hai fatto DI).

per esempio:

import { TranslateService } from "ng2-translate";

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

  // a bunch of awesome User methods
}

quindi da qualche componente relativo all'utente che ha inserito TranslateService

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

Spero che questo aiuti quelli là fuori come me che stavano per scrivere un codice molto più difficile da mantenere perché a volte siamo troppo intelligenti =]

(NOTA: il motivo per cui potresti voler impostare una variabile invece di renderla parte del tuo metodo di costruzione è che potresti avere casi in cui non è necessario utilizzare il servizio, quindi essere sempre obbligato a passarlo significherebbe introdurre importazioni extra / codice che non viene mai realmente utilizzato)


e magari creare translateServiceun get / set in cui getgenera un errore significativo invece di un'eccezione NullReference se ti sei dimenticato di impostarlo
Simon_Weaver

1
Forse ha più senso rendere translateService un parametro richiesto nel costruttore della classe? Questo modello è più in linea con il modello DI imho.
Icycool

16

Questo è un po '( molto ) hacky, ma ho pensato di condividere anche la mia soluzione. Nota che questo funzionerà solo con i servizi Singleton (iniettati nella radice dell'app, non nel componente!), Dal momento che vivono quanto la tua applicazione e c'è solo un'istanza di essi.

Primo, al tuo servizio:

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

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

Quindi in qualsiasi classe:

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

Questa soluzione è utile se si desidera ridurre la confusione di codice e non si utilizzano comunque servizi non singleton.


Ma come si usa MyService in MyClass senza alcuna dichiarazione nel costruttore?
Akhil V

@AkhilV devi solo importare MyService come importeresti qualsiasi altra classe, quindi puoi chiamare direttamente il metodo come descritto.
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
}

1
Non sono sicuro di quale sia la migliore pratica per lo scenario di OP, ma questa risposta sembra molto più semplice di quelle principali. Spostare la injector.get()chiamata al modulo funzionerà (supponendo che un servizio senza stato in modo che diverse classi possano "condividere" la stessa istanza)?
G0BLiN

Penso che il codice finirebbe con tutte le istanze poney che condividono la stessa istanza del servizio poney. Cosa pensi?
Julien

Julien - questo è un risultato accettabile (in realtà quello preferito) per il mio scenario - pensa a qualcosa come un validatore di input, un gestore di autorizzazioni utente o un servizio di localizzazione - in cui hai bisogno della stessa funzionalità in tutta l'app ma non c'è bisogno di un'istanza unica per ciascuno singolo consumatore del servizio.
G0BLiN

Problemi nel tentativo di farlo funzionare nei test Jasmine. Come configurare la configurazione del test? ServiceLocator.injector continua a restituire null, anche se ho inserito il "PoneyService" nel TestBed?
GGizmos

3

Se i metodi di servizio sono funzioni pure, un modo pulito per risolvere questo problema è avere membri statici nel servizio.

il tuo servizio

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

la tua classe

export class MyClass{
  test(){
     MyService.dosomething(); //no need to inject in constructor
  }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.