【问题标题】: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();
    }
}

【问题讨论】:

标签: javascript angular


【解决方案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 中对我不起作用。

      我采用了这种方法:

      1. 引用 @ComponentA 的 providers 字段中的所有服务。
      2. 在 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 ){}
          }
          

          【讨论】:

            猜你喜欢
            • 2019-11-04
            • 1970-01-01
            • 2023-03-16
            • 2016-07-21
            • 1970-01-01
            • 2017-02-09
            • 2016-08-07
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多