Pobieranie wystąpienia usługi bez iniekcji konstruktora

81

Mam @Injectableusługę zdefiniowaną w bootstrapie. Chcę uzyskać wystąpienie usługi bez użycia iniekcji konstruktora. Próbowałem użyć, ReflectiveInjector.resolveAndCreateale wydaje się, że tworzy nową instancję.

Powodem, dla którego próbuję zrobić, jest to, że mam podstawowy komponent pochodzący z wielu komponentów. Teraz potrzebuję dostępu do usługi, ale nie chcę jej dodawać do ctora, ponieważ nie chcę wstrzykiwać usługi do wszystkich składników pochodnych.

TLDR: Potrzebuję ServiceLocator.GetInstance<T>()

AKTUALIZACJA: Zaktualizowany kod dla RC5 +: Przechowywanie instancji wtryskiwacza do użycia w komponentach

dstr
źródło

Odpowiedzi:

72

Tak, ReflectiveInjector.resolveAndCreate()tworzy nową i niepołączoną instancję wtryskiwacza.

Możesz wstrzyknąć Injectorinstancję Angulars i pobrać z niej żądaną instancję za pomocą

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

Możesz również przechowywać Injectorw jakiejś zmiennej globalnej, a następnie użyć tej instancji wtryskiwacza, aby uzyskać dostarczone instancje, na przykład jak wyjaśniono w https://github.com/angular/angular/issues/4112#issuecomment-153811572

Günter Zöchbauer
źródło
1
Przechowywanie wtryskiwacza w zmiennej globalnej w bootstrap.then () brzmi tak, jak potrzebuję.
dstr
65
Pytanie brzmi bez konstruktora, co sprawia, że ​​ta odpowiedź jest raczej nieprzydatna.
Jonathan Miles
1
Jeśli konstruktor znajduje się w komponencie, to jest to wtryskiwacz komponentów, w usłudze jest to wtryskiwacz modułu (w zasadzie root injector, jeśli nie jest to leniwie ładowany moduł). Możesz również uzyskać wtryskiwacz składników macierzystych, dodając @SkipSelf(). Możesz także powiązać parentgetter, aby uzyskać wtryskiwacz root.
Günter Zöchbauer
2
@Rhyous magic? Możesz udostępnić wtryskiwacz w polu statycznym lub zmiennej globalnej i uzyskać do niego dostęp, ale najpierw musisz wstrzyknąć go „gdzieś” za pomocą konstruktora i przypisać do pola statycznego.
Günter Zöchbauer
1
Czy kontener DI jest tak zły, że nie obsługuje nawet podstawowych funkcji wspólnego kontenera DI?
Rhyous
61

W zaktualizowanym Angular, w którym używane są ngModules, możesz utworzyć zmienną dostępną w dowolnym miejscu kodu:

export let AppInjector: Injector;

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

Teraz możesz użyć, AppInjectoraby znaleźć dowolną usługę w dowolnym miejscu kodu.

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

const myService = AppInjector.get(MyService);
Elias Rezer
źródło
1
Miły. Może być dobrą odpowiedzią na stackoverflow.com/questions/39409328/... również.
Arjan
1
Ty jesteś królem!
Davide Perozzi,
Wow, czy możemy to zrobić?
Simon_Weaver
2
To ma mały problem. Musisz stworzyć osobne wtryskiwacze dla leniwie ładowanych modułów, które dostarczają usługę.
Vahid
7

Inne podejście polegałoby na zdefiniowaniu niestandardowego dekoratora (a CustomInjectabledo ustawienia metadanych do wstrzykiwania zależności:

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

Wykorzysta metadane z konstruktora nadrzędnego zamiast własnych. Możesz go użyć na klasie podrzędnej:

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

Zobacz to pytanie, aby uzyskać więcej informacji:

Thierry Templier
źródło
2
Problem polega na tym, że tsc nie pozwala mi wywołać super (argumenty), ponieważ argumenty konstruktora podstawowego nie są zgodne.
parlament
1
w tym konkretnym przypadku możemy po prostu usunąć konstruktora z klasy pochodnej 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);
    }
  }

component.ts

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

 StoreService.getInstance().MyAlertMethod();
Mahi
źródło
Jak to działa z usługami, które wymagają innych usług wstrzykniętych w konstruktorze?
Emeke Ajeh