Как внедрить сервис в класс (а не в компонент)

88

Я хочу внедрить службу в класс, который не является компонентом .

Например:

Myservice

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

Мои занятия

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

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

Это решение не работает (я думаю, потому что MyClassеще не было создано).

Есть ли другой способ использовать службу в классе (не в компоненте)? Или вы сочтете мой дизайн кода неуместным (использовать службу в классе, который не является компонентом)?

Спасибо.

Elec
источник

Ответы:

56

Инъекции работают только с классами, экземпляры которых создаются посредством внедрения зависимостей Angulars (DI).

  1. Вам нужно
    • добавить @Injectable()к MyClassи
    • предоставить MyClassкак providers: [MyClass]в компоненте или NgModule.

Когда вы затем вводите MyClassкуда-то, MyServiceэкземпляр передается, MyClassкогда он создается с помощью DI (до того, как он вводится в первый раз).

  1. Альтернативный подход - настроить индивидуальный инжектор, например
constructor(private injector:Injector) { 
  let resolvedProviders = ReflectiveInjector.resolve([MyClass]);
  let childInjector = ReflectiveInjector.fromResolvedProviders(resolvedProviders, this.injector);

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

Таким образом, myClassбудет MyClassэкземпляр, созданный Angulars DI, myServiceкоторый будет внедрен при создании MyClassэкземпляра.
См. Также Получение зависимости от инжектора вручную внутри директивы

  1. Еще один способ - создать экземпляр самостоятельно:
constructor(ms:myService)
let myClass = new MyClass(ms);
Гюнтер Цёхбауэр
источник
2
Я считаю, что внедрение сервиса в отдельный класс - это антипаттерн.
tomexx
11
Конечно, но иногда это все еще имеет смысл, и всегда полезно знать, что возможно,
Гюнтер Цохбауэр
Зачем создавать в конструкторе новый инжектор? Почему бы не использовать тот, который был введен? Если вы собираетесь создать новый, то нет причин вводить его в первую очередь
smac89
Новый - это дочерний инжектор с дополнительными провайдерами. Если поставщики, добавленные к дочернему инжектору, зависят от поставщиков, которые предоставляются в родительских компонентах или на уровне модуля, то DI может решить все это автоматически. Поэтому определенно есть ситуации, когда в этом есть смысл.
Günter Zöchbauer
1
@TimothyPenz благодарит за редактирование. В этом примере privateнет необходимости, потому что injectorне используется вне конструктора, поэтому нет необходимости хранить ссылку в поле.
Günter Zöchbauer
29

Это не прямой ответ на вопрос, но если вы читаете эту ТАК по моей причине, это может помочь ...

Допустим, вы используете ng2-translate и действительно хотите, чтобы он был у вашего User.tsкласса. Вы сразу же думаете, что нужно использовать DI, чтобы вставить его, в конце концов, вы делаете Angular. Но это своего рода чрезмерное обдумывание, вы можете просто передать его в свой конструктор или сделать его общедоступной переменной, которую вы установили из компонента (где вы, вероятно, сделали DI).

например:

import { TranslateService } from "ng2-translate";

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

  // a bunch of awesome User methods
}

затем из некоторого связанного с пользователем компонента, который внедрил TranslateService

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

Надеюсь, это поможет тем, кто вроде меня собирался писать намного сложнее для поддержки кода, потому что иногда мы слишком умны =]

(ПРИМЕЧАНИЕ: причина, по которой вы можете захотеть установить переменную вместо того, чтобы делать ее частью вашего метода конструктора, заключается в том, что у вас могут быть случаи, когда вам не нужно использовать службу, поэтому всегда требуется передавать ее, что означало бы введение дополнительного импорта / код, который на самом деле никогда не используется)

Райан Крюс
источник
и, возможно, сделать translateServiceget / set, в котором getвместо исключения NullReference выдается значимая ошибка, если вы забыли его установить
Simon_Weaver
1
Может, имеет смысл сделать translateService обязательным параметром в конструкторе класса? Этот шаблон больше соответствует шаблону DI imho.
Icycool
15

Это вроде ( очень ) хакерство, но я подумал, что тоже поделюсь своим решением. Обратите внимание, что это будет работать только со службами Singleton (внедренными в корень приложения, а не в компонент!), Поскольку они существуют до тех пор, пока ваше приложение, и их только один экземпляр.

Во-первых, к вашим услугам:

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

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

Тогда в любом классе:

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

Это решение хорошо, если вы хотите уменьшить беспорядок в коде и в любом случае не используете службы, отличные от singleton.

Килвс
источник
Но как использовать MyService в MyClass без объявления в конструкторе?
Ахил V
@AkhilV, вы просто импортируете MyService, как если бы вы импортировали любой другой класс, затем вы можете напрямую вызвать метод, как описано.
Килвес,
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
Не уверен, что лучше всего подходит для сценария OP, но этот ответ кажется намного проще, чем ведущие. Будет ли injector.get()работать перемещение вызова в модуль (при условии, что служба без отслеживания состояния позволяет нескольким классам "совместно использовать" один и тот же экземпляр)?
G0BLiN
Я думаю, что этот код закончится тем, что все экземпляры poney будут использовать один и тот же экземпляр сервиса poney. Что вы думаете?
Жюльен
Жюльен - это приемлемый результат (на самом деле предпочтительный) для моего сценария - подумайте о чем-то вроде валидатора ввода, обработчика разрешений пользователя или службы локализации - где вам нужны одинаковые функции во всем приложении, но нет необходимости в уникальном экземпляре для каждого индивидуальный потребитель услуги.
G0BLiN
3

Если ваши методы службы являются чистыми функциями, чистый способ решить эту проблему - иметь в службе статические члены.

ваш сервис

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

твой класс

export class MyClass{
  test(){
     MyService.dosomething(); //no need to inject in constructor
  }
}
dasfdsa
источник