在 Angular 2(Beta)中将一项服务注入另一项服务的最佳方法是啥?

Posted

技术标签:

【中文标题】在 Angular 2(Beta)中将一项服务注入另一项服务的最佳方法是啥?【英文标题】:What's the best way to inject one service into another in angular 2 (Beta)?在 Angular 2(Beta)中将一项服务注入另一项服务的最佳方法是什么? 【发布时间】:2016-04-20 15:32:08 【问题描述】:

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

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

export class MyFirstSvc 



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


export class MyThirdSvc 
    constructor() 
        this.helpfulService = new MyFirstSvc();
    

【问题讨论】:

阅读blog.thoughtram.io/angular/2015/09/17/… 这个答案可以帮到你angular service inject 【参考方案1】: “提供”您的服务,位于您打算使用它们的位置或上面的某个位置,例如,如果每个服务只有一个实例(单例),您可以使用 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]);

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;
  

请参阅 Plunker 了解其他文件。

Service DI 有点奇怪的是它仍然依赖于组件。例如,MySecondSvc 是在组件请求它时创建的,并且取决于在组件树中“提供”MyFirstSvc 的位置,这可能会影响将哪个MyFirstSvc 实例注入MySecondSvc。此处对此进行了更多讨论:Can you only inject services into services through bootstrap?

【讨论】:

【参考方案2】:

服务被认为是在组件之间共享的。所以假设我有一项服务,我可以在不同的组件中使用它。

在此答案中,我向您展示了一项服务,该服务接受来自一个组件的数据并将该数据发送到其他组件。

我使用了路由、共享服务、共享对象的概念。 我希望这能帮助您了解共享服务的基础知识。

注意:@Injectable 装饰器用于使服务可注入。

Answer

Boot.ts

import Component,bind from 'angular2/core';

import bootstrap from 'angular2/platform/browser';

import Router,ROUTER_PROVIDERS,RouteConfig, ROUTER_DIRECTIVES,APP_BASE_HREF,LocationStrategy,RouteParams,ROUTER_BINDINGS from 'angular2/router';

import SharedService from 'src/sharedService';

import ComponentFirst from 'src/cone';
import ComponentTwo from 'src/ctwo';


@Component(
  selector: 'my-app',
  directives: [ROUTER_DIRECTIVES],
  template: `
    <h1>
      Home
    </h1> 

    <router-outlet></router-outlet>
      `,

)

@RouteConfig([
  path:'/component-first', name: 'ComponentFirst', component: ComponentFirst
  path:'/component-two', name: 'ComponentTwo', component: ComponentTwo

])

export class AppComponent implements OnInit 

  constructor(router:Router)
  
    this.router=router;
  

    ngOnInit() 
    console.log('ngOnInit'); 
    this.router.navigate(['/ComponentFirst']);
  





    bootstrap(AppComponent, [SharedService,
    ROUTER_PROVIDERS,bind(APP_BASE_HREF).toValue(location.pathname)
    ]);

第一组件

import Component,View,bind from 'angular2/core';
import SharedService from 'src/sharedService';
import Router,ROUTER_PROVIDERS,RouteConfig, ROUTER_DIRECTIVES,APP_BASE_HREF,LocationStrategy,RouteParams,ROUTER_BINDINGS from 'angular2/router';
@Component(
  //selector: 'f',
  template: `
    <div><input #myVal type="text" >
    <button (click)="send(myVal.value)">Send</button>
      `,

)

export class ComponentFirst   

  constructor(service:SharedService,router:Router)
    this.service=service;
    this.router=router;
  

  send(str)
    console.log(str);
    this.service.saveData(str); 
    console.log('str');
    this.router.navigate(['/ComponentTwo']);
  


第二组件

import Component,View,bind from 'angular2/core';
import SharedService from 'src/sharedService';
import Router,ROUTER_PROVIDERS,RouteConfig, ROUTER_DIRECTIVES,APP_BASE_HREF,LocationStrategy,RouteParams,ROUTER_BINDINGS from 'angular2/router';
@Component(
  //selector: 'f',
  template: `
    <h1>myName</h1>
    <button (click)="back()">Back<button>
      `,

)

export class ComponentTwo   

  constructor(router:Router,service:SharedService)
  
    this.router=router;
    this.service=service;
    console.log('cone called');
    this.myName=service.getData();
  
  back()
  
     console.log('Back called');
    this.router.navigate(['/ComponentFirst']);
  


SharedService 和共享对象

import Component, Injectable,Input,Output,EventEmitter from 'angular2/core'

// Name Service
export interface myData 
   name:string;




@Injectable()
export class SharedService 
  sharingData: myData=name:"nyks";
  saveData(str)
    console.log('save data function called' + str + this.sharingData.name);
    this.sharingData.name=str; 
  
  getData:string()
  
    console.log('get data function called');
    return this.sharingData.name;
  
 

【讨论】:

【参考方案3】:

是的,第一件事是在您要注入的每个服务上添加@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 

组件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; 
  

Service1Service2 两个服务:Service1ChildComponent 使用,Service2Service1 使用

@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 注入器。它可以“看到”应用程序注入器中定义的元素。这意味着如果在此提供程序中找不到提供程序,它将自动在此父注入器中查找。如果后者找不到,则会抛出“provider not found”错误。 ChildComponent 注入器将遵循与 AppComponent 相同的规则。要注入组件执行的注入链中涉及的元素,将首先在此注入器中查找提供程序,然后在 AppComponent 中查找,最后在应用程序中查找。

这意味着当尝试将Service1 注入ChildComponent 构造函数时,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

希望对您有所帮助(抱歉,答案很长), 蒂埃里

【讨论】:

我觉得这是一个很好的答案!我对您说的一句话感到有些困惑:“可注入的名称有点阴险。这并不意味着该类将是“可注入的”,而是会进行装饰,以便可以注入构造函数参数”..因为,在您的Service1 试图注入 Service2,所以你需要有 @injectable 装饰你的 service1,这样你的 service2 才能被注入(我从 service1 中删除了可注入装饰器,然后代码将不起作用)。我对么?我只是想确认一下。谢谢:-) @GeorgeHuang,是的,如果一个服务依赖于另一个服务,则需要@Injectable() @thierry 如果我们想在所有其他组件中使用公共组件怎么办,我的意思是如何通过整个应用程序提供所有其他组件共有的组件? @Pardeep 你的意思是没有在组件的指令属性中定义每次? 您可以在平台指令中添加它们。请参阅此链接:github.com/angular/angular/issues/5938。【参考方案4】:

在连接 ComponentA -> ServiceB -> ServiceC 时,@Injectable 在 Angular 2.0.0-beta.17 中对我不起作用。

我采用了这种方法:

    引用 @ComponentA 的 providers 字段中的所有服务。 在 ServiceB 中,使用构造函数中的 @Inject 注解连接 ServiceC。

运行 this Plunker 以查看示例或查看下面的代码

app.ts

@Component(selector: 'my-app',
    template: `Hello! This is my app <br/><br/><overview></overview>`,
    directives: [OverviewComponent]
)
class AppComponent 

bootstrap(AppComponent);

overview.ts

import Component, bind from 'angular2/core';
import OverviewService from "../services/overview-service";
import PropertiesService from "../services/properties-service";

@Component(
    selector: 'overview',
    template: `Overview listing here!`,
    providers:[OverviewService, PropertiesService] // Include BOTH services!
)

export default class OverviewComponent 

    private propertiesService : OverviewService;

    constructor( overviewService: OverviewService) 
        this.propertiesService = overviewService;
        overviewService.logHello();
    

overview-service.ts

import PropertiesService from "./properties-service";
import Inject from 'angular2/core';

export class OverviewService 

    private propertiesService:PropertiesService;

    // Using @Inject in constructor
    constructor(@Inject(PropertiesService) propertiesService:PropertiesService)
        this.propertiesService = propertiesService;
    

    logHello()
        console.log("hello");
        this.propertiesService.logHi();
    

properties-service.ts

// Using @Injectable here doesn't make a difference
export class PropertiesService 

    logHi()
        console.log("hi");
    

【讨论】:

如果构造函数参数的类型与传递给@Inject(...)的类型相同并且类具有@Injectable()(带有())注释,则使用@Inject(...)是多余的。跨度> 当我尝试这样做时,我得到“无法解析 OverviewService(?) 的所有参数”。检查plnkr.co/edit/g924s5KQ0wJW83Qiwu0e?p=preview【参考方案5】:

首先要做的是用@Injectable注解对所有服务进行注解。注意注释末尾的括号,没有这个解决方案将无法工作。

一旦完成,我们就可以使用构造函数注入将服务相互注入:

@Injectable()
export class MyFirstSvc 



@Injectable()
export class MySecondSvc 
    constructor(helpfulService: MyFirstSvc)         
    


@Injectable()
export class MyThirdSvc 
    constructor(helpfulService: MyFirstSvc)         
    

【讨论】:

【参考方案6】:

不确定是否仍然需要答案,所以我会继续尝试回答这个问题。

考虑以下示例,其中我们有一个组件,它使用服务在其模板中填充一些值,如下所示

testComponent.component.ts

import  Component  from "@angular/core"
import  DataService  from "./data.service"
@Component(
    selector:"test-component",
    template:`<ul>
             <li *ngFor="let person of persons"> person.name </li>
             </ul>
)

export class TestComponent 
  persons:<Array>;
  constructor(private _dataService:DataService)
    this.persons = this._dataService.getPersons()
  

上面的代码非常简单,它会尝试获取 getPersons 从 DataService 返回的任何内容。 DataService 文件在下面可用。

data.service.ts

export class DataService 

persons:<Array>;

constructor()
    this.persons = [
      name: "Apoorv",
      name: "Bryce",
      name: "Steve"
    ]


getPersons()

return this.persons


上面的代码可以在不使用@Injectable 装饰器的情况下完美运行。但是当我们的服务(在这种情况下为 DataService)需要一些依赖项时,问题就会开始,例如。网址。如果我们改变我们的data.service.ts文件如下我们会得到一个错误说Cannot resolve all parameters for DataService(?). Make sure they all have valid type or annotations.

import  Http  from '@angular/http';
export class DataService 

persons:<Array>;

constructor()
    this.persons = [
      name: "Apoorv",
      name: "Bryce",
      name: "Steve"
    ]


getPersons()

return this.persons


这与 Angular 2 中装饰器的工作方式有关。请阅读https://blog.thoughtram.io/angular/2015/05/03/the-difference-between-annotations-and-decorators.html 以深入了解此问题。

上面的代码也不会工作,因为我们也必须在我们的引导模块中导入 HTTP。

但我可以建议的一条经验法则是,如果您的服务文件需要依赖项,那么您应该使用装饰器 @Injectable 装饰该类。

参考:https://blog.thoughtram.io/angular/2015/09/17/resolve-service-dependencies-in-angular-2.html

【讨论】:

【参考方案7】:

首先你需要提供你的服务

您可以在引导方法中提供它:

bootstrap(AppComponent,[MyFirstSvc]);

或在应用程序组件上,或在任何其他组件中,取决于您的需要。:

@Component(
    ...
      providers:[MyFirstSvc]

...

然后只需使用构造函数为您注入服务:

export class MySecondSvc 
      constructor(private myFirstSvc : MyFirstSvc )

【讨论】:

以上是关于在 Angular 2(Beta)中将一项服务注入另一项服务的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不能在 Angular 2 应用程序中将 RouterStateSnapshot 注入到登录组件中?

如何在 Angular 2 的同一服务中将一些硬编码数据与 API 一起使用?

在 Angular 1.5 中将模块注入业力测试

如何在 Angular 2 beta 的服务中有效地使用 Http 组件?

Angular 2 将一个类注入到服务中

如何使用构造函数上的异步调用注入服务,Angular 2