Подтвердить что ты не робот

Angular 2: включение службы в класс

У меня есть класс angular, который представляет форму. Я хочу, чтобы иметь возможность создавать экземпляры нескольких экземпляров этого класса с помощью конструктора.

Конструктор принимает несколько аргументов, представляющих свойства этой формы.

constructor(public center: Point, public radius: number, fillColor: string,
    fillOpacity: number, strokeColor: string, strokeOpacity: number, zIndex: number)

Внутри моего класса я хочу использовать сервис, который предоставляет возможность рисовать фигуры на карте. Можно ли внедрить эту службу в мой класс и по-прежнему использовать конструктор стандартным способом.

Итак, я хочу сделать что-то вроде ниже и angular автоматически разрешить вложенную зависимость.

constructor(public center: GeoPoint, public radius: number, 
    fillColor: string, fillOpacity: number, strokeColor: string, strokeOpacity: number, 
    zIndex: number, @Inject(DrawingService) drawingService: DrawingService)
4b9b3361

Ответ 1

Мне удалось решить мою проблему.

Угол 2 - 4 обеспечивает отражающий инжектор, который позволяет вводить зависимости вне параметров конструктора.

Все, что мне нужно было сделать, это импортировать отражающий инжектор из @angular/core.

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

А потом:

let injector = ReflectiveInjector.resolveAndCreate([DrawingService]);
this.drawingApi = injector.get(DrawingService);

Класс даже не должен быть украшен декоратором @Injectable. Единственная проблема заключается в том, что мне нужно предоставить все зависимости для DrawingService и все вложенные зависимости, так что это сложно поддерживать.

РЕДАКТИРОВАТЬ:

Угловой 5

import { Injector } from "@angular/core";

const injector = Injector.create([
    { provide: DrawingService }
]);
this.drawingApi = injector.get(DrawingService);

Угловой 6

import { Injector } from "@angular/core";

const injector = Injector.create({ 
  providers: [ 
    { provide: DrawingService },
  ]
});
this.drawingApi = injector.get(DrawingService);

Ответ 2

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

Первый подход - использование менеджера для ваших объектов или объектов без обслуживания

У вас есть один или несколько сервисов factory, которые/отвечают за создание объектов.

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

Например, предположим, что у вас есть сущности как иерархия классов:

abstract class Entity { }

class SomeEntity extends Entity { 
   ...
}

Затем вы можете иметь EntityManager, который является сервисом, и может создавать объекты:

@Injectable()   // is a normal service, so DI is standard
class EntityManager {

  constructor(public http: Http) { }    // you can inject any services now

  create<E extends Entity>(entityType: { new(): E; }): E {
    const entity = new entityType();    // create a new object of that type
    entity.manager = this;              // set itself on the object so that that object can access the injected services like http - one can also just pass the services not the manager itself
    return entity;
  }

}

Вы также можете иметь параметры конструкции, если хотите (но у них не будет никакой информации о типе, поскольку create должен работать со всеми типами сущностей):

class SomeEntity extends Entity { 
   constructor(param1, param1) { ... }
}

// in EntityManager
create<E extends Entity>(entityType: { new(): E; }, ...params): E {
    const entity = new entityType(...params);
    ...
}

Теперь ваши сущности могут объявить менеджера:

abstract class Entity {
  manager: EntityManager;
}

И ваши сущности могут использовать его для выполнения любых действий:

class SomeEntity extends Entity {
  doSomething() {
    this.manager.http.request('...');
  }
}

Теперь каждый раз, когда вам нужно создать объект/объект, вы используете этот менеджер. EntityManager нужно вводить сам, но сущности - это свободные объекты. Но все angular код начнется с контроллера или службы или, таким образом, можно будет ввести менеджера.

// service, controller, pipe, or any other angular-world code

constructor(private entityManager: EntityManager) {
    this.entity = entityManager.create(SomeEntity);
}

Этот подход также может быть адаптирован к произвольным объектам. Вам не нужна иерархия классов, но с typescript это работает лучше. Также имеет смысл иметь некоторый базовый класс для ваших объектов, так как вы можете повторно использовать код и этот способ старой моды, особенно в области/объектно-ориентированном подходе.

PROS. Этот подход более безопасен, поскольку он все еще находится на полной иерархии DI и должен быть менее нежелательный побочный эффект.

CONS. Недостатком является то, что вы никогда не сможете использовать new снова и не сможете получить доступ к этим службам в произвольном коде. Вы всегда должны полагаться на DI и на своих factory/фабриках.

Второй подход - h4ckz0rs

Вы создаете службу, предназначенную для получения (через DI) услуг, которые вам нужны в объектах.

Эта часть очень похожа на первый подход, просто эта служба не является factory. Вместо этого он передает внедренные службы в объект, который определен за пределами этого класса в другом файле (объяснение после). Например:

...
import { externalServices } from './external-services';

@Injectable()
export class ExternalServicesService {

  constructor(http: Http, router: Router, someService: SomeService, ...) {
    externalServices.http = http;
    externalServices.router = router;
    externalServices.someService = someService;
  }

}

Объект, который будет содержать сервисы, определяется в собственном файле следующим образом:

export const externalServices: {
  http,
  router,
  someService
} = { } as any;

Обратите внимание, что службы не используют информацию о типе (это недостаток, но необходим).

Затем вы должны убедиться, что ExternalServicesService вводится один раз. Лучше всего использовать основной компонент приложения:

export class AppComponent {

  constructor(..., externalServicesService: ExternalServicesService) {

Наконец, теперь вы можете использовать службы в любом произвольном объекте в любой момент после создания основного компонента приложения.

import { externalServices } from '../common/externalServices' // or wherever is defined

export class SomeObject() {
    doSomething() {
        externalServices.http().request(...) // note this will be called after ng2 app is ready for sure
    }
}

Обратите внимание, что вы не сможете вызвать какую-либо из этих служб в коде класса или в объектах, не созданных после создания приложения. Но в типичном приложении это никогда не понадобится.

Теперь несколько пояснений об этой странной установке:

Зачем использовать объект externalServices в отдельном файле вместо одного и того же файла или просто сохранять службы самого класса (как статические атрибуты) и почему службы непечатаны?

Причина в том, что при расширении кода, например. через angular -cli/webpack с режимом --prod, он, скорее всего, получит циклические зависимости, которые не могут быть правильно решены, и вы получите уродливые ошибки, которые трудно найти -  Я уже прошел через это:).

Ошибка формы

Невозможно прочитать свойство 'prototype' из undefined

видно только при запуске с флагом --prod, намекает на то, что зависимости не устранены правильно.

Намного лучше убедиться, что ExternalServicesService зависит только от externalServices, чтобы передать экземпляры службы, и приложение только вводит ExternalServicesService один раз (например, в ваш основной AppComponent), тогда все произвольные коды/объекты будут использовать только externalServices для получения услуг.

Таким образом, любому такому коду нужно будет импортировать externalServices, у которого нет дальнейших отпечатков (потому что службы также не печатаются). Если бы они импортировали ExternalServicesService, он бы импортировал все остальное и не смог бы статически ставить двунаправленные отпечатки. И это становится серьезной проблемой в ng2/webpack при наборе для prod.

То же самое произойдет, если мы будем использовать типы для сервисов, потому что для этого потребуется imports.

ПРОФИ: Этот подход проще использовать после установки, и вы можете использовать new. В принципе, любой файл кода может импортировать externalServices и иметь мгновенный доступ к тем службам, которые вы хотите открыть таким образом.

CONS: Недостатком является хакерская настройка и возможные проблемы, вызванные циклическими нажатиями. Это также более чувствительно, так как вы не можете быть уверены, что externalServices имеет эти услуги сразу. Они будут определены только после запуска приложения ng2 и сначала будет введена ExternalServicesService. Недостатком является также то, что у вас больше нет информации о типе этих сервисов.


PS: Я не уверен, почему эта тема не более популярна.

Например, являясь фанатом ориентированного на домен проектирования, наличие мощных объектов (например, методов, направленных на вызовы REST или взаимодействия с другими службами) имеет важное значение, и это ограничение всегда затрудняло.

Нам пришлось преодолеть это ограничение как в angularjs, так и теперь снова в Angular2 +, поскольку он, кажется, еще не рассматривается в библиотеке.

Ответ 3

По состоянию на Angular 5.x:

import { Injector } from "@angular/core";
export class Model {

    static api: Api;

    constructor(data: any) {

        // check the api ref not exist
        // We don't want to initiate a new object every time
        if (!Model.api){
            //try inject my api service which use the HttpClient
            const injector: any = Injector.create([{ provide: Api, useClass: Api, deps: [] }]);
            Model.api = injector.get(Api);
        }

        // .....

    }
}

Ответ 4

На самом деле вы не можете. Класс должен быть украшен @Injectable, чтобы Angular2 вводил вещи. Декоратор @Inject является "только", чтобы указать дополнительные метаданные о том, что нужно вводить.

В вашем случае класс управляется вами, так как большинство его параметров конструктора не соответствуют зависимостям и предоставляются, когда вы явно создаете экземпляр класса.