Obtener instancia de servicio sin inyección de constructor

81

Tengo un @Injectableservicio definido en bootstrap. Quiero obtener la instancia del servicio sin usar la inyección del constructor. Intenté usar ReflectiveInjector.resolveAndCreatepero eso parece crear una nueva instancia.

La razón por la que intento hacerlo es que tengo un componente base derivado de muchos componentes. Ahora necesito acceder a un servicio, pero no quiero agregarlo al ctor porque no quiero inyectar el servicio en todos los componentes derivados.

TLDR: necesito un ServiceLocator.GetInstance<T>()

ACTUALIZACIÓN: código actualizado para RC5 +: almacenamiento de la instancia del inyector para su uso en componentes

dstr
fuente

Respuestas:

72

Sí, ReflectiveInjector.resolveAndCreate()crea una instancia de inyector nueva y no conectada.

Puede inyectar la Injectorinstancia de Angulars y obtener la instancia deseada de ella usando

constructor(private injector:Injector) {
  injector.get(MyService);
}

También puede almacenar Injectoren alguna variable global y luego usar esta instancia de inyector para adquirir instancias proporcionadas, por ejemplo, como se explica en https://github.com/angular/angular/issues/4112#issuecomment-153811572

Günter Zöchbauer
fuente
1
Almacenar inyector en una variable global en bootstrap.then () suena como lo que necesito.
Dstr
65
La pregunta dice sin un constructor, lo que hace que esta respuesta sea bastante inútil.
Jonathan Miles
1
Si el constructor está en un componente, es el inyector de componentes, en un servicio es el inyector de módulo (básicamente inyector de raíz si no es un módulo de carga diferida). También puede obtener el inyector de componentes principales agregando @SkipSelf(). También puede interar el parentcaptador para obtener el inyector raíz.
Günter Zöchbauer
2
@Rhyous magia? Puede compartir el inyector en un campo estático o variable global y acceder a él desde allí, pero primero debe inyectarlo "en algún lugar" usando un constructor y asignarlo al campo estático.
Günter Zöchbauer
1
¿Es el contenedor DI tan malo que ni siquiera admite las características básicas de un contenedor DI común?
Rhyous
61

En el Angular actualizado donde se usan ngModules, puede crear una variable disponible en cualquier parte del código:

export let AppInjector: Injector;

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

Ahora, puede usar el AppInjectorpara encontrar cualquier servicio en cualquier parte de su código.

import {AppInjector} from '../app.module';

const myService = AppInjector.get(MyService);
Elias Rezer
fuente
1
Agradable. También podría ser una buena respuesta en stackoverflow.com/questions/39409328/… .
Arjan
1
¡Eres el rey!
Davide Perozzi
¿Se nos permite hacer esto?
Simon_Weaver
2
Esto tiene un pequeño problema. Tienes que hacer inyectores separados para módulos de carga diferida que brinden un servicio.
Vahid
7

Otro enfoque consistiría en definir un decorador personalizado (a CustomInjectablepara configurar los metadatos para la inyección de dependencia:

export function CustomComponent(annotation: any) {
  return function (target: Function) {

    // DI configuration
    var parentTarget = Object.getPrototypeOf(target.prototype).constructor;
    var parentAnnotations = Reflect.getMetadata('design:paramtypes', parentTarget);

    Reflect.defineMetadata('design:paramtypes', parentAnnotations, target);

    // Component annotations / metadata
    var annotations = Reflect.getOwnMetadata('annotations', target);
    annotations = annotations || [];
    annotations.push(annotation);
    Reflect.defineMetadata('annotations', annotations, target);
  }
}

Aprovechará los metadatos del constructor principal en lugar de los suyos propios. Puedes usarlo en la clase infantil:

@Injectable()
export class SomeService {
  constructor(protected http:Http) {
  }
}

@Component()
export class BaseComponent {
  constructor(private service:SomeService) {
  }
}

@CustomComponent({
  (...)
})
export class TestComponent extends BaseComponent {
  constructor() {
    super(arguments);
  }

  test() {
    console.log('http = '+this.http);
  }
}

Consulte esta pregunta para obtener más detalles:

Thierry Templier
fuente
2
El problema es que tsc no me permite llamar a super (argumentos) porque los argumentos para el constructor base no coinciden.
parlamento
1
en ese caso particular, podemos eliminar el constructor de la clase derivada github.com/Microsoft/TypeScript/issues/12439
slowkot
-1

StoreService .ts

  import { Injectable} from '@angular/core';

    @Injectable()
    export class StoreService {
      static isCreating: boolean = false;
      static instance: StoreService ;

      static getInstance() {
        if (StoreService.instance == null) {
          StoreService.isCreating = true;
          StoreService.instance = new StoreService ();
          StoreService.isCreating = false;
        }
        return StoreService.instance;
      }
      constructor() {
        if (!StoreService.isCreating) {
          throw new Error('You can\'t call new in Singleton instances! Call StoreService.getInstance() instead.');
        }
     }

  MyAlertMethod(){
    alert('hi);
    }
  }

componente.ts

//call this service directly in component.ts as below:-

 StoreService.getInstance().MyAlertMethod();
Mahi
fuente
¿Cómo puede funcionar esto con servicios que requieren otros servicios inyectados en el constructor?
Emeke Ajeh