Angular 2:将服务注入课堂

时间:2016-07-11 14:27:36

标签: typescript dependency-injection 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)

5 个答案:

答案 0 :(得分:37)

我设法解决了我的问题。

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

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

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

然后:

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

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

修改

Angular 5

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

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

Angular 6

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

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

答案 1 :(得分:16)

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

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

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

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

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

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

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

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

这种方法也可以适应任意对象。您不需要类层次结构,但使用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;
  }

}

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

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

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

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

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而不是同一个文件,或者只是在类本身上保存服务(作为静态属性),为什么服务是无类型的? < / p>

原因是,当您对代码进行制作时,例如通过使用--prod模式的angular-cli / webpack,它很可能会得到无法正确解析的循环依赖,并且您会收到难以找到的丑陋错误 -  我已经完成了这个:)。

表单错误

  

无法读取属性&#39;原型&#39;未定义的

只有在使用--prod标志运行时才会看到提示无法正确解析依赖关系这一事实。

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

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

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

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

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

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

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

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

答案 2 :(得分:5)

从Angular 5.x开始:

class LobbyDetailViewButton(generic.DetailView):
    # this should be where the button url connects
    model = Lobby

    # use dispatch for your code
    def dispatch(self,request,*args,**kwargs):
        lobby_object = self.get_object()
        user_object = request.user
        # do stuff with lobby_object & user_object
        return super().dispatch(request,*args, **kwargs) 
        # can also return an http request

答案 3 :(得分:0)

有vojtajina在GitHub上的帖子,提供了解决此问题的好方法。 这个答案只是一个链接,但最好在上下文中阅读,因为那里还有其他有趣的信息:

https://github.com/angular/di.js/issues/22#issuecomment-36773343

答案 4 :(得分:-3)

事实上,你不能。该类必须使用@Injectable进行修饰,以使Angular2注入内容。 @Inject装饰器“仅”在那里指定有关注入内容的其他元数据。

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