将angular色2(testing版)中的服务注入另一个服务的最佳方式是什么?

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

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

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

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

这是我对注射机制的理解。 为类设置@Injectable装饰器时,Angular会尝试为当前执行链的注入器中的相应types创build或获取实例。 事实上,Angular2应用程序不仅有一个注入器,而且还有一个注入器树。 它们隐含地关联到整个应用程序和组件。 这个级别的一个关键特征是它们以层级方式连接在一起。 这个注入器树映射组件的树。 没有注射器被定义为“服务”。

我们来看一个例子。 我有以下应用程序:

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

     @Component({ selector: 'my-app', template: ` <child></child> `, (...) directives: [ ChildComponent ] }) export class AppComponent { } 
  • Component ChildComponent :将在AppComponent组件中使用的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; } } 
  • 两个服务, Service1ChildComponentService1ChildComponent使用, ChildComponent使用Service1

     @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 

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

  • 应用程序注入器可以使用bootstrapfunction的第二个参数进行configuration
  • 可使用此组件的providers属性configuration的AppComponent注入器。 它可以“查看”应用程序注入器中定义的元素。 这意味着如果提供者没有在这个提供者中find,它将自动寻find这个父注入器。 如果在后者中找不到,则会抛出“提供者未find”错误。
  • ChildComponent注入器将遵循与AppComponent相同的规则。 要注入参与执行组件的注入链中的元素,将首先在该注入器中查找提供者,然后在AppComponent者,最后在应用程序中查找提供者。

这意味着当试图将Service1注入到ChildComponent构造函数中时,Angular2会查看ChildComponent注入器,然后进入AppComponent ,最后进入应用程序之一。

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

这意味着Service1Service2都可以根据您的需求,使用组件的providers属性和应用程序注入器的bootstrapfunction的第二个参数在每个级别指定。

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

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

所以它是非常强大的,你可以自由组织,只要你想和你的需求。

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

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

希望它可以帮助你(对不起长时间的回答),Thierry

  • 在你打算使用它们的地方或者以上的某个地方“提供”你的服务,比如你可以使用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获取其他文件。

服务DI有点奇怪,它仍然依赖于组件。 例如, MySecondSvc是在组件请求它时创build的,并且取决于MyFirstSvc在组件树中“提供”的位置,这可能会影响将哪个MyFirstSvc实例注入到MySecondSvc 。 这里讨论更多: 你只能通过引导注入服务到服务?

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

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

我已经使用了路由,共享服务,共享对象的概念。 我希望这可以帮助你了解共享服务的基础。

注意: @注射装饰是用来使服务注射。

回答

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) ]); 

FirstComponent

 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']); } } 

SecondComponent

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

不知何故@Injectable不适用于Angular 2.0.0-beta.17,当连接ComponentA – > ServiceB – > ServiceC。

我采取了这种做法:

  1. 引用@ComponentA's providers字段中的所有服务。
  2. 在ServiceB中,使用构造函数中的@Inject注释来连接ServiceC。

运行此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(); } } 

概述 – 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(); } } 

属性 – service.ts

 // Using @Injectable here doesn't make a difference export class PropertiesService { logHi(){ console.log("hi"); } } 

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

考虑下面的例子,我们有一个组件,它使用一个服务来填充模板中的一些值,如下所示

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() } } 

上面的代码非常简单,它将尝试从DataService中获取任何getPersons的返回值。 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. 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。

但我可以build议的一个拇指规则是,如果你的服务文件需要一个依赖,那么你应该用装饰器@Injectable来装饰这个类。

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

首先要做的是用@Injectable注释来注释所有的服务。 注意注释末尾的括号,没有这个解决scheme将无法正常工作。

一旦完成,我们可以使用构造器注入将服务注入彼此。

 @Injectable() export class MyFirstSvc { } @Injectable() export class MySecondSvc { constructor(helpfulService: MyFirstSvc) { } } @Injectable() export class MyThirdSvc { constructor(helpfulService: MyFirstSvc) { } } 

首先你需要提供你的服务

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

 bootstrap(AppComponent,[MyFirstSvc]); 

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

 @Component({ ... providers:[MyFirstSvc] } ... 

然后使用构造函数注入你的服务:

 export class MySecondSvc { constructor(private myFirstSvc : MyFirstSvc ){} }