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 utiliser L'ai pour faire circuler des services en dehors des composants?
en d'autres termes, Je ne veux pas faire cela:
export class MyFirstSvc {
}
export class MySecondSvc {
constructor() {
this.helpfulService = new MyFirstSvc();
}
}
export class MyThirdSvc {
constructor() {
this.helpfulService = new MyFirstSvc();
}
}
7 réponses
Oui, la première chose est d'ajouter le décorateur @Injectable
sur chaque service que vous voulez injecter. En fait, le nom Injectable
est un peu insidieux. Cela ne signifie pas que la classe sera "injectable" mais il ornera donc les paramètres du constructeur peut être injecté. 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 configuration d'un @Injectable
décorateur pour une classe, angulaire va essayer de créer ou d'obtenir des instances pour les types correspondants dans l'injecteur pour la chaîne d'exécution courante. 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'ils sont liés d'une manière hiérarchique. Cet arbre d'injecteurs cartographie l'arbre des composants. Aucun injecteur n'est défini pour les "services".
prenons un échantillon. J'ai l'application suivante:
-
Component
AppComponent
: le principal composant de mon application qui est fourni lors de la création de L'application Angular2 dans labootstrap
fonction@Component({ selector: 'my-app', template: ` <child></child> `, (...) directives: [ ChildComponent ] }) export class AppComponent { }
-
Composant
ChildComponent
: un sous-élément qui sera utilisé au sein de laAppComponent
composant@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
etService2
:Service1
est utilisé parChildComponent
etService2
parService1
@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 qui peut être configuré en utilisant le second paramètre de la
bootstrap
fonction - l'injecteur
AppComponent
qui peut être configuré en utilisant l'attributproviders
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 rechercher dans cet injecteur parent. Si ce n'est pas le cas, une erreur "provider not found" sera générée. - le
ChildComponent
injecteur qui suivra les mêmes règles que leAppComponent
. Pour injecter les éléments impliqués dans la chaîne d'injection exécutée pour le composant, les fournisseurs seront recherchés d'abord dans cet injecteur, puis dans leAppComponent
et enfin dans l'application.
cela signifie qu'en essayant d'injecter le Service1
dans le ChildComponent
constructeur, Angular2 regardera dans le ChildComponent
injecteur, puis dans le AppComponent
l'un et enfin dans l'application.
depuis Service2
doit être injecté dans Service1
, le même traitement de résolution sera fait: ChildComponent
injecteur, AppComponent
un et une application.
cela signifie que les deux 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 deuxième paramètre de la fonction bootstrap
pour le application de l'injecteur.
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 correspondante créée sera partagée par l'ensemble de l'application (tous les composants, tous les services, ...).
- Si vous définissez un fournisseur au niveau du composant, l'instance sera partagée par le composant lui-même, de ses sous composants et tous les "services" impliqués dans la chaîne de dépendances.
donc c'est très puissant et vous êtes libre d'organiser comme vous voulez et pour vos besoins.
voici le plunkr 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 .
espère qu'il vous aide (et désolé la longue réponse), Thierry
- "Fournir" vos services, quelque part au-dessus de l'endroit où vous comptez l'utiliser, par exemple, vous pouvez les placer à la racine de votre application à l'aide de
bootstrap()
si vous avez seulement une fois instance de chaque service (singletons). - utilisez le
@Injectable()
décorateur sur tout service qui dépend d'un autre. - injecter les autres services dans le constructeur du service dépendant.
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]);
MYS Secondsvc.ts
import {Injectable} from 'angular2/core';
import {MyFirstSvc} from '../services/MyFirstSvc';
@Injectable()
export class MySecondSvc {
constructor(private _firstSvc:MyFirstSvc) {}
getValue() {
return this._firstSvc.value;
}
}
voir Plunker pour les autres fichiers.
ce qui est un peu bizarre à propos du Service DI, c'est qu'il dépend encore de composants. Par exemple, MySecondSvc
est créé lorsqu'un composant le demande, et selon l'endroit où MyFirstSvc
a été "fourni" dans l'arbre des composants, cela peut affecter l'instance MyFirstSvc
injectée dans MySecondSvc
. Ceci est discuté plus ici: pouvez-vous seulement injecter des services dans les services par bootstrap?
est considéré comme étant partagé entre les composantes. 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 envoie ces données à un autre composant.
j'ai utilisé le concept de routage, de service partagé, D'objet partagé. J'espère que cela vous aidera à comprendre les bases du service par actions.
Note: @Injectable Décorateur est utilisé pour rendre le service injectable.
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;
}
}
D'une façon ou D'une autre @Injectable ne fonctionne pas pour moi en angle 2.0.0-beta.17 lors du câblage ComponentA -> ServiceB -> ServiceC.
j'ai pris cette approche:
- référence tous les services dans le champ fournisseurs de @ComponentA.
- dans ServiceB utilisez l'annotation @Inject dans le constructeur pour raccorder ServiceC.
Exécuter ce Plunker voir un exemple ou un code de vue ci-dessous
app.ts
@Component({selector: 'my-app',
template: `Hello! This is my app <br/><br/><overview></overview>`,
directives: [OverviewComponent]
})
class AppComponent {}
bootstrap(AppComponent);
vue d'ensemble.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();
}
}
vue d'ensemble-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();
}
}
propriétés-service.ts
// Using @Injectable here doesn't make a difference
export class PropertiesService {
logHi(){
console.log("hi");
}
}
Je ne suis pas sûr qu'une réponse soit toujours requise, alors j'essaierai d'y répondre.
prenons l'exemple suivant, où nous avons un Composant qui utilise un service pour remplir 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 va essayer de récupérer n'importe quel retour getPersons du service de données. 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 morceau de code ci-dessus fonctionnera parfaitement bien sans l'utilisation du décorateur @Injectable. Mais le problème commencera quand notre service (DataService dans ce cas) exigera certaines dépendances comme par exemple: Http. si nous changeons 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 façon dont les décorateurs fonctionnent en angle 2. Veuillez lire https://blog.thoughtram.io/angular/2015/05/03/the-difference-between-annotations-and-decorators.html pour avoir une compréhension approfondie de cette question.
le code ci-dessus ne fonctionnera pas non plus car nous devons aussi importer HTTP dans notre module bootstrap.
mais une règle empirique que je peux suggérer est que si votre dossier de service a besoin d'une dépendance alors vous devriez décorer cette classe avec un décorateur @Injectable.
référence: https://blog.thoughtram.io/angular/2015/09/17/resolve-service-dependencies-in-angular-2.html
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 fonctionnera pas.
une fois que cela est fait, nous pouvons alors injecter des services dans l'autre en utilisant l'injection du constructeur:
@Injectable()
export class MyFirstSvc {
}
@Injectable()
export class MySecondSvc {
constructor(helpfulService: MyFirstSvc) {
}
}
@Injectable()
export class MyThirdSvc {
constructor(helpfulService: MyFirstSvc) {
}
}
vous devez D'abord fournir votre service
vous pouvez le Fournir soit dans la méthode bootstrap:
bootstrap(AppComponent,[MyFirstSvc]);
ou le sur le composant app, ou dans tout autre composant, selon vos besoins.:
@Component({
...
providers:[MyFirstSvc]
}
...
alors il suffit de vous injecter le service en utilisant le constructeur:
export class MySecondSvc {
constructor(private myFirstSvc : MyFirstSvc ){}
}