Получение экземпляра службы без внедрения конструктора

81

У меня есть @Injectableслужба, определенная в начальной загрузке. Я хочу получить экземпляр службы без использования внедрения конструктора. Я пробовал использовать, ReflectiveInjector.resolveAndCreateно, похоже, создал новый экземпляр.

Причина, по которой я пытаюсь это сделать, заключается в том, что у меня есть базовый компонент, полученный из многих компонентов. Теперь мне нужно получить доступ к сервису, но я не хочу добавлять его в ctor, потому что я не хочу внедрять сервис во все производные компоненты.

TL; DR: мне нужен ServiceLocator.GetInstance<T>()

ОБНОВЛЕНИЕ: Обновленный код для RC5 +: Сохранение экземпляра инжектора для использования в компонентах

dstr
источник

Ответы:

72

Да, ReflectiveInjector.resolveAndCreate()создается новый неподключенный экземпляр инжектора.

Вы можете ввести Injectorэкземпляр Angulars и получить от него желаемый экземпляр, используя

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

Вы также можете сохранить Injectorв некоторой глобальной переменной, а затем использовать этот экземпляр инжектора для получения предоставленных экземпляров, например, как описано в https://github.com/angular/angular/issues/4112#issuecomment-153811572

Гюнтер Цёхбауэр
источник
1
Сохранение инжектора в глобальной переменной в bootstrap.then () похоже на то, что мне нужно.
dstr
65
В вопросе говорится без конструктора, что делает этот ответ довольно бесполезным.
Джонатан Майлз
1
Если конструктор находится в компоненте, это инжектор компонентов, в службе - инжектор модуля (в основном корневой инжектор, если это не ленивый загруженный модуль). Вы также можете получить инжектор родительских компонентов, добавив @SkipSelf(). Вы также можете взаимодействовать с parentгеттером, чтобы получить корневой инжектор.
Günter Zöchbauer
2
@ Роковая магия? Вы можете поделиться инжектором в статическом поле или глобальной переменной и получить к нему доступ оттуда, но сначала вам нужно ввести его «где-нибудь» с помощью конструктора и назначить его статическому полю.
Günter Zöchbauer
1
Неужели контейнер DI ng использует так плохо, что он даже не поддерживает основные функции обычного контейнера DI?
Rhyous
61

В обновленном Angular, где используются ngModules, вы можете создать переменную, доступную в любом месте кода:

export let AppInjector: Injector;

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

Теперь вы можете использовать, AppInjectorчтобы найти любую службу в любом месте вашего кода.

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

const myService = AppInjector.get(MyService);
Элиас Резер
источник
1
Ницца. Также может быть хорошим ответом на stackoverflow.com/questions/39409328/… .
Arjan
1
Ты король!
Давид Пероцци
Ух ты, нам разрешено это делать?
Simon_Weaver
2
Это небольшая проблема. Вы должны сделать отдельные инжекторы для лениво загружаемых модулей, которые предоставляют сервис.
Вахид
7

Другой подход состоял бы в определении настраиваемого декоратора (a CustomInjectableдля установки метаданных для внедрения зависимости:

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);
  }
}

Он будет использовать метаданные из родительского конструктора вместо своих собственных. Вы можете использовать его в дочернем классе:

@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);
  }
}

См. Этот вопрос для получения более подробной информации:

Тьерри Темплиер
источник
2
Проблема в том, что tsc не позволяет мне вызывать super (аргументы), потому что аргументы для базового конструктора не совпадают.
парламент
1
в этом конкретном случае мы можем просто удалить конструктор из производного класса github.com/Microsoft/TypeScript/issues/12439
slowkot 05
-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);
    }
  }

component.ts

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

 StoreService.getInstance().MyAlertMethod();
Махи
источник
Как это может работать с сервисами, которым требуются другие сервисы, внедренные в конструктор?
Emeke Ajeh