web-dev-qa-db-fra.com

Quelle est la meilleure façon d'injecter un service dans un autre dans angular 2 (Beta)?

Je sais comment injecter un service dans un composant (via @Component), mais comment puis-je utiliser l'ID pour transmettre des services en dehors des composants?

En d'autres termes, je ne veux pas faire ceci:

export class MyFirstSvc {

}

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

export class MyThirdSvc {
    constructor() {
        this.helpfulService = new MyFirstSvc();
    }
}
39
Bryce Johnson

Oui, la première chose à faire est d’ajouter le décorateur @Injectable à chaque service que vous souhaitez injecter. En fait, le nom Injectable est un peu insidieux. Cela ne signifie pas que la classe sera "injectable" mais elle décorera afin que les paramètres du constructeur puissent être injectés. Voir ce numéro de github pour plus de détails: https://github.com/angular/angular/issues/4404 .

Voici ma compréhension du mécanisme d'injection. Lors de la définition d'un décorateur @Injectable pour une classe, Angular essaiera de créer ou d'obtenir des instances pour les types correspondants dans l'injecteur pour la chaîne d'exécution en cours. En fait, il n'y a pas qu'un seul injecteur pour une application Angular2, mais un arbre d'injecteurs. Ils sont implicitement associés à l'ensemble de l'application et des composants. Une caractéristique clé à ce niveau est qu’elles sont liées entre elles de manière hiérarchique. Cet arbre d'injecteurs mappe l'arbre de composants. Aucun injecteur n'est défini pour "services".

Prenons un échantillon. J'ai l'application suivante:

  • Composant AppComponent: composant principal de mon application fourni lors de la création de l'application Angular2 dans la fonction bootstrap

    @Component({
      selector: 'my-app', 
        template: `
          <child></child>
        `,
        (...)
        directives: [ ChildComponent ]
    })
    export class AppComponent {
    }
    
  • Composant ChildComponent: un sous-composant qui sera utilisé dans le composant 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; 
      }
    }
    
  • Deux services, Service1 et Service2: Service1 sont utilisés par les variables ChildComponent et Service2 de 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' }
        ];
      }
    }
    

Voici un aperçu de tous ces éléments et de leurs relations:

Application
     |
AppComponent
     |
ChildComponent
  getData()     --- Service1 --- Service2

Dans une telle application, nous avons trois injecteurs:

  • L'injecteur d'application pouvant être configuré à l'aide du deuxième paramètre de la fonction bootstrap
  • L'injecteur AppComponent qui peut être configuré à l'aide de l'attribut providers de ce composant. Il peut "voir" les éléments définis dans l'injecteur d'application. Cela signifie que si un fournisseur n’est pas trouvé dans ce fournisseur, il sera automatiquement recherché dans cet injecteur parent. S'il n'est pas trouvé dans le dernier cas, une erreur "fournisseur introuvable" sera renvoyée.
  • L'injecteur ChildComponent qui suivra les mêmes règles que celui de AppComponent. Pour injecter des éléments impliqués dans la chaîne d'injection exécutée pour le composant, les fournisseurs seront d'abord recherchés dans cet injecteur, puis dans celui-ci à la variable AppComponent et enfin dans celui de l'application.

Cela signifie que pour tenter d’injecter le Service1 dans le constructeur ChildComponent, Angular2 examinera l’injecteur ChildComponent, puis celui AppComponent et finalement celui-ci dans l’application.

Étant donné que Service2 doit être injecté dans Service1, le même traitement de résolution sera effectué: ChildComponent injector, AppComponent one et application one.

Cela signifie que Service1 et Service2 peuvent être spécifiés à chaque niveau en fonction de vos besoins en utilisant l'attribut providers pour les composants et le second paramètre de la fonction bootstrap pour l'injecteur d'application.

Cela permet de partager des instances de dépendances pour un ensemble d’éléments:

  • Si vous définissez un fournisseur au niveau de l'application, l'instance créée avec correspoding sera partagée par l'ensemble de l'application (tous les composants, tous les services, ...).
  • Si vous définissez un fournisseur au niveau d'un composant, l'instance sera partagée par le composant lui-même, ses sous-composants et tous les "services" impliqués dans la chaîne de dépendance.

C'est donc très puissant et vous êtes libre d'organiser ce que vous voulez et selon vos besoins.

Voici le dossier correspondant pour que vous puissiez jouer avec: https://plnkr.co/edit/PsySVcX6OKtD3A9TuAEw?p=preview .

Ce lien de la documentation Angular2 pourrait vous aider: https://angular.io/docs/ts/latest/guide/hierarchical-dependency-injection.html .

En espérant que cela vous aide (et pardon pour la longue réponse), Thierry

46
Thierry Templier

Le service est considéré comme partagé entre les composants. Alors disons que si j'ai un service, je peux l'utiliser dans différents composants. 

Ici, dans cette réponse, je vous montre un service qui accepte les données d'un composant et les envoie à un autre composant.

J'ai utilisé le concept de routage, service partagé, objet partagé . J'espère que cela vous aidera à comprendre la base du service partagé.

Remarque: @Injectable decorater est utilisé pour rendre le service injectable. 

Réponse

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 et objet partagé

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

je ne sais pas si une réponse est toujours nécessaire, alors j'essaierais d'y répondre.

Prenons l'exemple suivant où nous avons un composant qui utilise un service pour renseigner certaines valeurs dans son modèle, comme ci-dessous.

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

Le code ci-dessus est assez simple et il essaiera d'extraire tout ce que getPersons retournera du DataService. Le fichier DataService est disponible ci-dessous.

data.service.ts

export class DataService {

persons:<Array>;

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

getPersons(){

return this.persons

}

Le code ci-dessus fonctionnera parfaitement sans l'utilisation du décorateur @Injectable. Mais le problème va commencer quand notre service (DataService dans ce cas) nécessite des dépendances comme par exemple. Http. si nous modifions notre fichier data.service.ts comme ci-dessous, nous obtiendrons une erreur disant 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

}

Cela a quelque chose à voir avec la manière dont les décorateurs fonctionnent dans Angular 2. Veuillez lire https://blog.iblytram.io/angular/2015/05/03/the-difference-between-annotations-and-decorators.html pour bien comprendre ce problème. 

Le code ci-dessus ne fonctionnera pas non plus, car nous devons également importer HTTP dans notre module d'amorçage. 

Mais une règle de base que je peux suggérer est que si votre fichier de service nécessite une dépendance, vous devez décorer cette classe avec un décorateur @Injectable.

référence: https://blog.blingtram.io/angular/2015/09/17/resolve-service-dependencies-in-angular-2.html

2
Apoorv

Quelque part, @Injectable ne fonctionne pas dans Angular 2.0.0-beta.17 lors du câblage ComponentA -> ServiceB -> ServiceC.

J'ai pris cette approche: 

  1. Référencez tous les services dans le champ fournisseurs de @ ComponentA.
  2. Dans ServiceB, utilisez l'annotation @Inject dans le constructeur pour câbler ServiceC.

Run this Plunker pour voir un exemple ou voir le code ci-dessous

app.ts

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

bootstrap(AppComponent);

aperçu.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");
    }
}
2
Julius

La première chose à faire est d'annoter tous les services avec l'annotation @Injectable. Notez les parenthèses à la fin de l'annotation, sans cela cette solution ne fonctionnerait pas.

Une fois cela fait, nous pouvons ensuite nous injecter des services les uns aux autres en utilisant l'injection de constructeur:

@Injectable()
export class MyFirstSvc {

}

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

@Injectable()
export class MyThirdSvc {
    constructor(helpfulService: MyFirstSvc) {        
    }
}
0
Angular University

Vous devez d'abord fournir votre service

Vous pouvez le fournir soit dans la méthode bootstrap:

bootstrap(AppComponent,[MyFirstSvc]);

ou le composant sur l'application, ou dans tout autre composant, selon vos besoins .:

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

puis juste vous injecter le service en utilisant le constructeur:

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