Typescript 角度2:将服务注入类

Typescript 角度2:将服务注入类,typescript,dependency-injection,angular,Typescript,Dependency Injection,Angular,我有一个代表形状的角度类。我希望能够使用构造函数实例化该类的多个实例 构造函数接受多个表示该形状属性的参数 constructor(public center: Point, public radius: number, fillColor: string, fillOpacity: number, strokeColor: string, strokeOpacity: number, zIndex: number) 在我的类中,我想使用提供在地图上绘制形状功能的服务。是否可以将该服务

我有一个代表形状的角度类。我希望能够使用构造函数实例化该类的多个实例

构造函数接受多个表示该形状属性的参数

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)

事实上,你不能。类必须用
@Injectable
修饰,才能让Angular2注入东西。
@Inject
装饰器“仅”用于指定有关注入内容的其他元数据


在您的例子中,该类由您管理,因为它的大多数构造函数参数不对应于依赖项,而是在您显式实例化该类时提供的。

我已经设法解决了我的问题

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

这里有两种其他可能的方法来实现期望的或非常相似的结果

第一种方法-对实体或非服务对象使用管理器 您有一个或多个工厂服务负责实例化您的对象

这意味着it可以进一步为对象提供所需的DEP,而不需要您自己传递它们

例如,假设您将实体作为类层次结构:

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

}
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
需要注入自身,但实体是自由对象。但所有角度代码都将从控制器或服务等开始,因此可以注入管理器

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

constructor(private entityManager: EntityManager) {
    this.entity = entityManager.create(SomeEntity);
}
这种方法也适用于任意对象。您不需要类层次结构,但使用typescript时效果更好。为对象设置一些基类也是有意义的,因为您也可以以这种旧式方式重用代码,特别是在面向域/对象的方法中

优点:这种方法更安全,因为它仍然存在于完整的DI层次结构中,并且应该有更少的不必要的副作用

缺点:缺点是您永远不能再使用
new
,也不能以任意代码访问这些服务。您始终需要依赖DI和您的工厂

第二进近-h4ckz0rs 您可以创建一个服务,专门用于获取(通过DI)对象中所需的服务

这一部分与第一种方法非常相似,只是此服务不是工厂。相反,它将注入的服务传递到一个对象中,该对象在这个类之外的另一个文件中定义(解释如下)。例如:

...
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”

只有在使用
--prod
标志运行时才能看到,这将提示依赖项未正确解析

最好确保
ExternalServicesService
只依赖
externalServices
传递服务实例,并且应用程序只注入
ExternalServicesService
一次(例如在主应用程序组件中)然后,所有任意代码/对象将仅使用
externalServices
获取服务

因此,任何这样的代码只需要导入
externalServices
,它没有进一步的dep(因为服务也没有被键入)。如果他们导入
externalservices服务
,它将导入其他所有内容,并且无法静态解析双向DEP。这成为ng2/webpack捆绑产品时的一个主要问题

如果我们为服务使用类型,也会发生同样的情况,因为这将需要
导入

优点:一旦设置完成,这种方法更容易使用,您可以自由使用
新的
。基本上,任何代码文件都可以
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);
        }

        // .....

    }
}