Como injetar serviço na classe (não componente)

88

Quero injetar um serviço em uma classe que não seja um componente .

Por exemplo:

Myservice

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

Minha classe

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

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

Esta solução não funciona (acho que porque MyClassainda não foi instanciada).

Existe outra maneira de usar um serviço em uma classe (não componente)? Ou você consideraria meu design de código inadequado (para usar um serviço em uma classe que não é um componente)?

Obrigado.

Elec
fonte

Respostas:

56

Injeções só funcionam com classes instanciadas por injeção de dependência (DI) de Angulars.

  1. Você precisa
    • adicionar @Injectable()a MyClasse
    • fornecer MyClasscomo providers: [MyClass]em um componente ou NgModule.

Quando você injeta em MyClassalgum lugar, uma MyServiceinstância é passada para MyClassquando é instanciada por DI (antes de ser injetada pela primeira vez).

  1. Uma abordagem alternativa é configurar um injetor personalizado como
constructor(private injector:Injector) { 
  let resolvedProviders = ReflectiveInjector.resolve([MyClass]);
  let childInjector = ReflectiveInjector.fromResolvedProviders(resolvedProviders, this.injector);

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

Desta forma myClass, será uma MyClassinstância, instanciada por Angulars DI, e myServiceserá injetada MyClassquando instanciada.
Veja também Obtendo dependência do Injector manualmente dentro de uma diretiva

  1. Outra maneira é criar você mesmo a instância:
constructor(ms:myService)
let myClass = new MyClass(ms);
Günter Zöchbauer
fonte
2
Acho que injetar serviço em uma classe autônoma é um antipadrão.
tomexx
11
Claro, mas às vezes ainda pode fazer sentido e é sempre bom saber o que é possível
Günter Zöchbauer
Por que criar um novo injetor no construtor? Por que não usar o que foi injetado? Se você vai criar um novo, então não há razão para ter um injetado em primeiro lugar
smac89
O novo é um injetor infantil com provedores adicionais. Se os provedores adicionados ao injetor filho dependerem de provedores que são fornecidos nos componentes pai ou no nível do módulo, então o DI pode resolver tudo automaticamente. Portanto, definitivamente existem situações em que isso faz sentido.
Günter Zöchbauer
1
@TimothyPenz obrigado pela edição. Neste exemplo, o privatenão é necessário, pois injectornão é usado fora do construtor, portanto, não é necessário manter uma referência em um campo.
Günter Zöchbauer
29

Não é uma resposta direta à pergunta, mas se você está lendo este SO pelo motivo que estou, isso pode ajudar ...

Digamos que você esteja usando o ng2-translate e realmente deseja que sua User.tsclasse o tenha. Seu pensamento imediato é usar DI para colocá-lo, você está fazendo Angular, afinal. Mas isso é meio que pensar demais, você pode simplesmente passar isso em seu construtor, ou torná-lo uma variável pública que você definiu a partir do componente (onde você presumivelmente fez o DI).

por exemplo:

import { TranslateService } from "ng2-translate";

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

  // a bunch of awesome User methods
}

em seguida, de algum componente relacionado ao usuário que injetou TranslateService

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

Espero que isso ajude aqueles que estão por aí, como eu, que estava prestes a escrever um código muito mais difícil para manter o código porque às vezes somos muito espertos =]

(NOTA: o motivo pelo qual você pode querer definir uma variável em vez de torná-la parte de seu método de construtor é que você pode ter casos em que não precisa usar o serviço, portanto, ser sempre solicitado a passá-lo significaria introduzir importações extras / código que nunca é realmente usado)

Ryan Crews
fonte
e talvez faça translateServiceum get / set onde o getlança um erro significativo em vez de uma exceção NullReference se você se esqueceu de defini-lo
Simon_Weaver
1
Talvez faça mais sentido tornar translateService um parâmetro obrigatório no construtor de classe? Este padrão está mais de acordo com o padrão DI imho.
Icycool
15

Isso é meio ( muito ) hacky, mas pensei em compartilhar minha solução também. Observe que isso só funcionará com serviços Singleton (injetados na raiz do aplicativo, não no componente!), Uma vez que eles duram tanto quanto o seu aplicativo e há apenas uma instância deles.

Primeiro, em seu serviço:

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

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

Então, em qualquer aula:

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

Esta solução é boa se você deseja reduzir a confusão de código e não está usando serviços não singleton de qualquer maneira.

Kilves
fonte
Mas como você usa MyService em MyClass sem qualquer declaração no construtor?
Akhil V
@AkhilV você apenas importa o MyService como importaria qualquer outra classe, então você pode chamar diretamente o método conforme descrito.
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
fonte
1
Não tenho certeza de qual é a melhor prática para o cenário do OP, mas essa resposta parece muito mais simples do que as principais. Mover a injector.get()chamada para o módulo funcionará (assumindo um serviço sem estado para que várias classes possam "compartilhar" a mesma instância)?
G0BLiN
Acho que esse código terminaria com todas as instâncias do poney compartilhando a mesma instância do serviço do poney. O que você acha?
Julien
Julien - esse é um resultado aceitável (na verdade, o preferido) para o meu cenário - pense em algo como um validador de entrada, gerenciador de permissões de usuário ou serviço de localização - onde você precisa da mesma funcionalidade em todo o aplicativo, mas não há necessidade de uma instância única para cada consumidor individual do serviço.
G0BLiN
3

Se seus métodos de serviço forem funções puras, uma maneira limpa de resolver isso é ter membros estáticos em seu serviço.

seu serviço

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

sua classe

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