Angular 2 - 服务 - 来自另一个服务的依赖注入

Angular 2 - service - dependency injection from another service

我在 Angular 2 中编写了两个服务。其中一个是 Http 的基本自定义 class,其中包含一些自定义功能(目前看起来很基本,但是它将扩展):

ServerComms.ts

import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';

@Injectable()
export class ServerComms {
    private url = 'myservice.com/service/';

    constructor (public http: Http) {
        // do nothing
    }

    get(options) {
        var req = http.get('https://' + options.name + url);

        if (options.timout) {
            req.timeout(options.timeout);
        }

        return req;
    }
}

另一个class,TicketService利用上面这个class,调用服务中的一个方法。定义如下:

TicketService.ts

import {Injectable} from 'angular2/core';
import {ServerComms} from './ServerComms';

@Injectable()
export class TicketService {
    constructor (private serverComms: ServerComms) {
        // do nothing
    }

    getTickets() {
        serverComms.get({
            name: 'mycompany',
            timeout: 15000
        })
            .subscribe(data => console.log(data));
    }
}

但是,每当我尝试这个时,我都会收到以下错误:

"No provider for ServerComms! (App -> TicketService -> ServerComms)"

我不明白为什么?我当然不需要注入每个其他服务所依赖的每项服务吗?这会变得很乏味吗?这在 Angular 1.x 中是可以实现的 - 我如何在 Angular 2 中实现同样的目标?

这样做正确吗?

当然可以。

在Angular2中,有多个注入器。注入器使用 providers 组件数组配置。当组件具有 providers 数组时,将在树中的那个点创建一个注入器。当组件、指令和服务需要解析它们的依赖关系时,它们会查找注入器树来找到它们。因此,我们需要使用提供程序配置该树。

在概念上,我喜欢认为有一个注入器树覆盖在组件树上,但它比组件树更稀疏。

同样,从概念上讲,我们必须配置此注入器树,以便依赖项 "provided" 在树中的适当位置。 Angular 2 不创建单独的树,而是重用组件树来执行此操作。所以尽管感觉我们是在组件树上配置依赖,但我更愿意认为我是在注入器树上配置依赖(它恰好覆盖了组件树,所以我必须使用组件来配置它)。

一尘不染?

Angular两个有注入器树的原因是允许非单例服务——即能够在注入器树的不同点创建特定服务的多个实例。如果您想要 Angular 类似 1 的功能(仅单例服务),请在您的根组件中提供所有服务。


构建您的应用程序,然后返回并配置注入器树(使用组件)。我喜欢这样想。如果您在另一个项目中重用组件,很可能需要更改 providers 数组,因为新项目可能需要不同的注入器树。

简而言之,由于注入器是在组件级别定义的,因此发起调用服务的组件必须看到相应的提供者。第一个(直接调用)还有另一个间接调用(由之前的服务调用)。

我们来打个比方。我有以下应用程序:

  • 组件AppComponent:在bootstrap函数中创建Angular2应用程序时提供的我的应用程序的主要组件

    @Component({
      selector: 'my-app', 
        template: `
          <child></child>
        `,
        (...)
        directives: [ ChildComponent ]
    })
    export class AppComponent {
    }
    
  • 组件ChildComponent:将在AppComponent组件

    中使用的子组件
    @Component({
        selector: 'child', 
        template: `
          {{data | json}}<br/>
          <a href="#" (click)="getData()">Get data</a>
        `,
        (...)
    })
    export class ChildComponent {
      constructor(service1:Service1) {
        this.service1 = service1;
      }
    
      getData() {
        this.data = this.service1.getData();
          return false; 
      }
    }
    
  • 两个服务,Service1Service2Service1ChildComponent使用,Service2被[=19=使用]

    @Injectable()
    export class Service1 {
      constructor(service2:Service2) {
        this.service2 = service2;
      }
    
      getData() {
        return this.service2.getData();
      }
    }
    

    @Injectable()
    export class Service2 {
    
      getData() {
        return [
          { message: 'message1' },
          { message: 'message2' }
        ];
      }
    }
    

以下是所有这些元素及其关系的概述:

Application
     |
AppComponent
     |
ChildComponent
  getData()     --- Service1 --- Service2

在这样的应用中,我们有三个注入器:

  • 可以使用bootstrap函数的第二个参数配置的应用程序注入器
  • 可以使用此组件的 providers 属性配置的 AppComponent 注入器。它可以 "see" 在应用程序注入器中定义的元素。这意味着如果在此提供程序中找不到提供程序,它将自动查找此父注入器。如果在后者中找不到,将抛出 "provider not found" 错误。
  • ChildComponent 注入器将遵循与 AppComponent 相同的规则。要注入组件执行的注入链中涉及的元素,将首先在此注入器中查找提供者,然后在 AppComponent 中查找,最后在应用程序中查找。

这意味着当尝试将 Service1 注入到 ChildComponent 构造函数时,Angular2 将查看 ChildComponent 注入器,然后是 AppComponent 注入器,最后进入申请一.

由于Service2需要注入Service1,所以会做同样的解析处理:ChildComponent注入器,AppComponent一个,应用一个。

这意味着 Service1Service2 都可以根据您的需要使用组件的 providers 属性和 [=16= 的第二个参数在每个级别指定] 应用程序注入器的功能。

这允许共享一组元素的依赖实例:

  • 如果您在应用程序级别定义提供者,则相应创建的实例将由整个应用程序(所有组件、所有服务等)共享。
  • 如果您在组件级别定义提供程序,则实例将由组件本身、其子组件以及依赖链中涉及的所有 "services" 共享。

所以它非常强大,您可以根据自己的需要自由组织。

这里是相应的plunkr所以你可以玩它:https://plnkr.co/edit/PsySVcX6OKtD3A9TuAEw?p=preview.

来自 Angular2 文档的 link 可以帮助您:https://angular.io/docs/ts/latest/guide/hierarchical-dependency-injection.html

嗯,我想你应该在全球范围内提供这两种服务:

bootstrap(App, [service1, service2]);

或提供给使用它们的组件:

@Component({providers: [service1, service2]})

@Injectable 装饰器添加必要的元数据来跟踪依赖关系,但不提供它们。