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

IT技术 javascript angular
2021-02-02 23:03:29

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

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

export class MyFirstSvc {

}

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

export class MyThirdSvc {
    constructor() {
        this.helpfulService = new MyFirstSvc();
    }
}
6个回答

是的,第一件事是@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 {
    }
    
  • Component 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:Service1ChildComponentService2使用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函数的第二个参数配置的应用程序注入器
  • AppComponent可以使用providers该组件属性配置注入器它可以“看到”应用程序注入器中定义的元素。这意味着如果在此提供程序中找不到提供程序,它将自动在此父注入器中查找。如果在后者中没有找到,则会抛出“provider not found”错误。
  • ChildComponent将遵循相同规则注入器AppComponent为了注入为组件执行的注入链中涉及的元素,将首先在此注入器中查找提供者,然后在注入器中查找AppComponent,最后在应用程序中查找

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

由于Service2需要注入到Service1,所以会做同样的解析处理:ChildComponentinjector, AppComponentone和application one。

这意味着,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 中删除了可注入的装饰器,那么代码将不起作用)。我对么?我只是想确认一下。谢谢 :-)
2021-03-11 23:03:29
@GeorgeHuang,是的,@Injectable()如果一项服务依赖于另一项服务则是必需的。
2021-03-18 23:03:29
@thierry 如果我们想在所有其他组件中使用通用组件怎么办,我的意思是如何通过整个应用程序提供所有其他组件通用的组件?
2021-03-31 23:03:29
您可以将它们添加到平台指令中。请参阅此链接:github.com/angular/angular/issues/5938
2021-04-02 23:03:29
@Pardeep 你的意思是没有在组件的指令属性中定义每次?
2021-04-04 23:03:29
  • 在您打算使用它们的地方或上方“提供”您的服务,例如,bootstrap()如果每个服务(单例)只有一个实例,您可以将它们放在应用程序的根目录下
  • @Injectable()在任何依赖于另一个服务的服务上使用装饰器。
  • 将其他服务注入到依赖服务的构造函数中。

启动文件

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. 这在此处讨论得更多:您能否仅通过引导程序将服务注入服务?

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

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

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

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

回答

引导文件

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

}

共享服务和共享对象

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

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

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

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.

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以获取深入了解这个问题。

上面的代码也不起作用,因为我们也必须在引导module中导入 HTTP。

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

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

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

我采取了这种方法:

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

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

应用程序

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

bootstrap(AppComponent);

概览.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");
    }
}
当我尝试这样做时,我收到“无法解析 OverviewService(?) 的所有参数”。检查plnkr.co/edit/g924s5KQ0wJW83Qiwu0e?p=preview
2021-03-13 23:03:29
@Inject(...)如果构造函数参数的类型与传递给的类型相同@Inject(...)并且类具有@Injectable()(with ()) 注释,使用是多余的
2021-04-03 23:03:29