Angular 2:将服务注入课堂

Lip*_*taa 26 dependency-injection typescript angular

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

构造函数采用多个参数来表示该形状的属性.

constructor(public center: Point, public radius: number, fillColor: string,
    fillOpacity: number, strokeColor: string, strokeOpacity: number, zIndex: number)
Run Code Online (Sandbox Code Playgroud)

在我的课程中,我想使用提供在地图上绘制形状的功能的服务.是否可以将该服务注入到我的类中,并仍然以标准方式使用构造函数.

所以我想做类似下面的事情并让Angular自动解决注入的依赖关系.

constructor(public center: GeoPoint, public radius: number, 
    fillColor: string, fillOpacity: number, strokeColor: string, strokeOpacity: number, 
    zIndex: number, @Inject(DrawingService) drawingService: DrawingService)
Run Code Online (Sandbox Code Playgroud)

Lip*_*taa 37

我设法解决了我的问题.

Angular 2 - 4提供反射注入器,允许在构造函数参数之外注入依赖项.

我所要做的就是从中导入反射式注射器@angular/core.

import {ReflectiveInjector} from '@angular/core';
Run Code Online (Sandbox Code Playgroud)

然后:

let injector = ReflectiveInjector.resolveAndCreate([DrawingService]);
this.drawingApi = injector.get(DrawingService);
Run Code Online (Sandbox Code Playgroud)

这个类甚至不必用@Injectable装饰器来装饰.唯一的问题是我必须为DrawingService和所有嵌套依赖项提供所有依赖项,因此很难维护.

编辑:

Angular 5

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

const injector = Injector.create([
    { provide: DrawingService }
]);
this.drawingApi = injector.get(DrawingService);
Run Code Online (Sandbox Code Playgroud)

Angular 6

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

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


Ovi*_*lha 16

以下是另外两种可能的方法来实现所需或非常相似的结果.

第一种方法 - 为您的实体或非服务对象使用管理器

您有一个或多个工厂服务负责实例化您的对象.

这意味着可以进一步提供对象所需的deps,并且不需要您自己传递它们.

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

abstract class Entity { }

class SomeEntity extends Entity { 
   ...
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以拥有一个服务的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;
  }

}
Run Code Online (Sandbox Code Playgroud)

如果您愿意,也可以使用构造参数(但由于create需要使用所有类型的实体,因此它们不具有任何类型信息):

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

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

您的实体现在可以声明经理:

abstract class Entity {
  manager: EntityManager;
}
Run Code Online (Sandbox Code Playgroud)

您的实体可以使用它来做任何事情:

class SomeEntity extends Entity {
  doSomething() {
    this.manager.http.request('...');
  }
}
Run Code Online (Sandbox Code Playgroud)

现在每次需要创建实体/对象时,都使用此管理器.在EntityManager需要被注入本身,但实体是免费的对象.但是所有角度代码都将从控制器或服务等开始,因此可以注入管理器.

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

constructor(private entityManager: EntityManager) {
    this.entity = entityManager.create(SomeEntity);
}
Run Code Online (Sandbox Code Playgroud)

这种方法也可以适应任意对象.您不需要类层次结构,但使用typescript可以更好地工作.为对象设置一些基类也是有意义的,因为您可以以旧的方式重用代码,尤其是在面向域/面向对象的方法中.

PROS:这种方法更安全,因为它仍然存在于完整的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;
  }

}
Run Code Online (Sandbox Code Playgroud)

将保存服务的对象在其自己的文件中定义如下:

export const externalServices: {
  http,
  router,
  someService
} = { } as any;
Run Code Online (Sandbox Code Playgroud)

请注意,服务不使用任何类型信息(这是一个缺点,但必要).

然后,您必须确保ExternalServicesService注入一次.最好的地方是使用主app组件:

export class AppComponent {

  constructor(..., externalServicesService: ExternalServicesService) {
Run Code Online (Sandbox Code Playgroud)

最后,现在您可以在实例化主应用程序组件后的任何位置使用任意对象中的服务.

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
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,在实例化应用程序后,您无法在类代码或未实例化的对象中调用任何这些服务.但在典型的应用程序中,永远不需要这样做.

现在,关于这个奇怪的设置的一些解释:

为什么externalServices在一个单独的文件中使用一个对象而不是同一个文件,或者只是在类本身上保存服务(作为静态属性)以及为什么服务是无类型的?

原因在于,当您通过angular-cli/webpack with --prodmode来构建代码时,很可能会得到无法正确解析的循环依赖,并且您将获得难以找到的难看的错误 - 我已经通过这个:).

表格的错误

无法读取undefined的属性'prototype'

只有在使用--prodflag 运行时才会看到提示依赖关系未正确解析的事实.

确保ExternalServicesService仅依赖于externalServices传递服务实例并且应用程序仅注入ExternalServicesService一次(例如在主AppComponent中)然后所有任意代码/对象将仅用于externalServices获取服务是更好的.

因此任何这样的代码只需要导入externalServices没有进一步deps的代码(因为服务也没有输入).如果他们要导入ExternalServicesService它将导入其他所有东西,并且无法静态解析双向deps.当捆绑产品时,这成为ng2/webpack中的一个主要问题.

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

PROS:这种方法在设置完成后更容易使用,并且您可以自由使用new.基本上任何代码文件都可以导入externalServices并以这种方式即时访问您要公开的服务.

缺点:缺点是hackish设置和循环deps引起的可能问题.它也更敏感,因为你不能确定externalServices这些服务是否正确.只有在ng2应用程序启动并ExternalServicesService首次注入时才会定义它们.缺点是您不再拥有有关这些服务的类型信息.


PS:我不确定为什么这个话题不再受欢迎.

例如,作为面向领域设计的粉丝,拥有强大的实体(例如,针对REST调用或与其他服务交互的方法)非常重要,而这种限制总是使其变得困难.

我们必须在angularjs和现在再次在Angular2 +中克服这个限制,因为它似乎仍然没有在库中解决.

  • 关于你的"PS":我绝对同意你的看法.有许多细微之处,低科技特色,......有棱有角.但是当谈到真正的单词问题时,你已经概述了角度并没有提供足够的支持. (7认同)

Eym*_*kum 6

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

        // .....

    }
}
Run Code Online (Sandbox Code Playgroud)


Thi*_*ier -3

事实上,你不能。该类必须用 来装饰,@Injectable以便 Angular2 注入东西。装饰@Inject器“仅”在那里指定有关注入内容的附加元数据。

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