在 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;
Service1
和 Service2
两个服务:Service1
被 ChildComponent
使用,Service2
被 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
在这样的应用程序中,我们有三个注入器:
可以使用bootstrap
函数的第二个参数配置的应用程序注入器
可以使用该组件的providers
属性配置的AppComponent
注入器。它可以“看到”应用程序注入器中定义的元素。这意味着如果在此提供程序中找不到提供程序,它将自动在此父注入器中查找。如果后者找不到,则会抛出“provider not found”错误。
ChildComponent
注入器将遵循与 AppComponent
相同的规则。要注入组件执行的注入链中涉及的元素,将首先在此注入器中查找提供程序,然后在 AppComponent
中查找,最后在应用程序中查找。
这意味着当尝试将Service1
注入ChildComponent
构造函数时,Angular2 将查看ChildComponent
注入器,然后查看AppComponent
注入器,最后进入应用程序之一。
由于Service2
需要注入Service1
,所以会做同样的解析处理:ChildComponent
注入器,AppComponent
一和应用一。
这意味着Service1
和Service2
可以根据您的需要使用组件的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 一起使用?