在角度2(Beta)中将一项服务注入另一项服务的最佳方法是什么?

Bry*_*son 41 javascript angular

我知道如何将服务注入组件(通过@Component),但是如何使用DI来传递组件之外的服务?

换句话说,我不想这样做:

export class MyFirstSvc {

}

export class MySecondSvc {
    constructor() {
        this.helpfulService = new MyFirstSvc();
    }
}

export class MyThirdSvc {
    constructor() {
        this.helpfulService = new MyFirstSvc();
    }
}
Run Code Online (Sandbox Code Playgroud)

Thi*_*ier 48

是的,首先要@Injectable在每个要注入的服务上添加装饰器.事实上,这个Injectable名字有点阴险.这并不意味着该类将是"可注入的",但它将进行装饰,因此可以注入构造函数参数.有关更多详细信息,请参阅此github问题:https://github.com/angular/angular/issues/4404.

以下是我对注射机制的理解.在@Injectable为类设置装饰器时,Angular将尝试为当前执行链的注入器中的相应类型创建或获取实例.实际上,Angular2应用程序不仅有一个注入器,而且还有一个注入器树.它们与整个应用程序和组件隐式关联.此级别的一个关键特性是它们以分层方式链接在一起.这个注入器树映射组件树.没有为"服务"定义注入器.

我们来试试吧.我有以下应用程序:

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

    @Component({
      selector: 'my-app', 
        template: `
          <child></child>
        `,
        (...)
        directives: [ ChildComponent ]
    })
    export class AppComponent {
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 组件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; 
      }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 两个服务,Service1Service2:Service1ChildComponentService2使用Service1

    @Injectable()
    export class Service1 {
      constructor(service2:Service2) {
        this.service2 = service2;
      }
    
      getData() {
        return this.service2.getData();
      }
    }
    
    Run Code Online (Sandbox Code Playgroud)
    @Injectable()
    export class Service2 {
    
      getData() {
        return [
          { message: 'message1' },
          { message: 'message2' }
        ];
      }
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

Application
     |
AppComponent
     |
ChildComponent
  getData()     --- Service1 --- Service2
Run Code Online (Sandbox Code Playgroud)

在这种应用中,我们有三个注射器:

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

这意味着当尝试将注入Service1ChildComponent构造函数中时,Angular2将查看注入ChildComponent器,然后进入注册器,AppComponent最后进入应用程序.

由于Service2需要注入Service1,因此将执行相同的分辨率处理:ChildComponent注入器,AppComponent一个和应用程序.

这意味着,Service1Service2根据使用您的需要可以在每个级别中指定providers为组件属性和的第二参数bootstrap为应用注射器功能.

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

  • 如果在应用程序级别定义提供程序,则整个应用程序将共享对应的已创建实例(所有组件,所有服务,...).
  • 如果在组件级别定义提供程序,则实例将由组件本身,其子组件以及依赖关系链中涉及的所有"服务"共享.

因此它非常强大,您可以根据自己的需要和需求自由组织.

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

Angular2文档中的这个链接可以帮助您:https://angular.io/docs/ts/latest/guide/hierarchical-dependency-injection.html .

希望它可以帮助你(对不起,答案很长),蒂埃里

  • @GeorgeHuang,是的,如果服务依赖于另一个服务,则需要`@Injectable()`. (3认同)
  • 我觉得这是一个很好的答案!我对你说的一句话感到有点困惑:"注入名称有点阴险.这并不意味着该类将是"可注射的"但它会装饰所以构造函数参数可以注入"..因为,在你的Service1试图注入Service2,所以你需要让@injectable装饰你的service1,这样就可以注入你的service2(我从service1中删除了注入装饰器,然后代码就不起作用了).我对么?我只是想确认一下.谢谢 :-) (2认同)
  • 您可以在平台指令中添加它们.请看这个链接:https://github.com/angular/angular/issues/5938. (2认同)

Mar*_*cok 5

  • 在您打算使用它们的地方或之上"提供"您的服务,例如,bootstrap()如果每个服务只有一个实例(单例),您可以将它们放在应用程序的根目录中.
  • @Injectable()在任何依赖于另一个的服务上使用装饰器.
  • 将其他服务注入到依赖服务的构造函数中.

boot.ts

import {bootstrap} from 'angular2/platform/browser';
import {AppComponent} from './app.component';
import {MyFirstSvc} from '../services/MyFirstSvc';
import {MySecondSvc} from '../services/MySecondSvc';

bootstrap(AppComponent, [MyFirstSvc, MySecondSvc]);
Run Code Online (Sandbox Code Playgroud)

MySecondSvc.ts

import {Injectable} from 'angular2/core';
import {MyFirstSvc} from '../services/MyFirstSvc';

@Injectable()
export class MySecondSvc {
  constructor(private _firstSvc:MyFirstSvc) {}
  getValue() {
    return this._firstSvc.value;
  }
}
Run Code Online (Sandbox Code Playgroud)

请参阅Plunker其他文件.

服务DI有点奇怪的是它依赖于组件.例如, MySecondSvc在组件请求它时创建,并且取决于MyFirstSvc组件树中"提供"的位置,这可能会影响MyFirstSvc注入哪个实例MySecondSvc.这里将对此进行更多讨论:您是否只能通过引导程序将服务注入服务?