43

I know how to inject a service into a component (via @Component), but how can I use DI to pass around services outside of components?

In other words, I don't want to do this:

export class MyFirstSvc {

}

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

export class MyThirdSvc {
    constructor() {
        this.helpfulService = new MyFirstSvc();
    }
}
Antikhippe
  • 5,395
  • 2
  • 22
  • 38
Bryce Johnson
  • 5,817
  • 5
  • 33
  • 47

7 Answers7

50

Yes, the first thing is to add the @Injectable decorator on each services you want to inject. In fact, the Injectable name is a bit insidious. It doesn't mean that the class will be "injectable" but it will decorate so the constructor parameters can be injected. See this github issue for more details: https://github.com/angular/angular/issues/4404.

Here is my understanding of the injection mechanism. When setting an @Injectable decorator for a class, Angular will try to create or get instances for corresponding types in the injector for the current execution chain. In fact, there is not only one injector for an Angular2 application but a tree of injectors. They are implicitly associated to the whole application and components. One key feature at this level is that they are linked together in a hierarchical way. This tree of injectors maps the tree of components. No injectors are defined for "services".

Let's take a sample. I have the following application:

  • Component AppComponent: the main component of my application that is provided when creating the Angular2 application in the bootstrap function

    @Component({
      selector: 'my-app', 
        template: `
          <child></child>
        `,
        (...)
        directives: [ ChildComponent ]
    })
    export class AppComponent {
    }
    
  • Component ChildComponent: a sub component that will be used within the AppComponent component

    @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; 
      }
    }
    
  • Two services, Service1 and Service2: Service1 is used by the ChildComponent and Service2 by 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' }
        ];
      }
    }
    

Here is an overview of all these elements and there relations:

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

In such application, we have three injectors:

  • The application injector that can be configured using the second parameter of the bootstrap function
  • The AppComponent injector that can be configured using the providers attribute of this component. It can "see" elements defined in the application injector. This means if a provider isn't found in this provider, it will be automatically look for into this parent injector. If not found in the latter, a "provider not found" error will be thrown.
  • The ChildComponent injector that will follow the same rules than the AppComponent one. To inject elements involved in the injection chain executed forr the component, providers will be looked for first in this injector, then in the AppComponent one and finally in the application one.

This means that when trying to inject the Service1 into the ChildComponent constructor, Angular2 will look into the ChildComponent injector, then into the AppComponent one and finally into the application one.

Since Service2 needs to be injected into Service1, the same resolution processing will be done: ChildComponent injector, AppComponent one and application one.

This means that both Service1 and Service2 can be specified at each level according to your needs using the providers attribute for components and the second parameter of the bootstrap function for the application injector.

This allows to share instances of dependencies for a set of elements:

  • If you define a provider at the application level, the correspoding created instance will be shared by the whole application (all components, all services, ...).
  • If you define a provider at a component level, the instance will be shared by the component itself, its sub components and all the "services" involved in the dependency chain.

So it's very powerful and you're free to organize as you want and for your needs.

Here is the corresponding plunkr so you can play with it: https://plnkr.co/edit/PsySVcX6OKtD3A9TuAEw?p=preview.

This link from the Angular2 documentation could help you: https://angular.io/docs/ts/latest/guide/hierarchical-dependency-injection.html.

Hope it helps you (and sorry the long answer), Thierry

Thierry Templier
  • 182,931
  • 35
  • 372
  • 339
  • 2
    I felt this is a great answer! I was a little confused about one sentence you said: "Injectable name is a bit insidious. It doesn't mean that the class will be "injectable" but it will decorate so the constructor parameters can be injected".. Since, in your Service1 was trying to inject Service2, so you need to have the @injectable decorating your service1, so your service2 can be injected(I removed the injectable decorator from the service1, then the code won't work). Am I correct? I just wanted to confirm. Thank you :-) – George Huang Mar 21 '16 at 01:46
  • 3
    @GeorgeHuang, yes, `@Injectable()` is required if a service depends on another service. – Mark Rajcok Apr 06 '16 at 17:35
  • 1
    @thierry what if we want to use common component in all another components, i mean how to provide component common to all another through whole app ? – Pardeep Jain Apr 08 '16 at 08:03
  • 1
    @Pardeep You mean without defining each time in the directives attribute of components? – Thierry Templier Apr 08 '16 at 08:06
  • 2
    You could add them in the platform directives. See this link: https://github.com/angular/angular/issues/5938. – Thierry Templier Apr 08 '16 at 08:11
5
  • "Provide" your services somewhere at or above where you intend to use them, e.g., you could put them at the root of your application using bootstrap() if you only one once instance of each service (singletons).
  • Use the @Injectable() decorator on any service that depends on another.
  • Inject the other services into the constructor of the dependent service.

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

See Plunker for other files.

What's a bit odd about Service DI is that it still depends on components. E.g., MySecondSvc is created when a component requests it, and depending on where MyFirstSvc was "provided" in the component tree, that can affect which MyFirstSvc instance is injected into MySecondSvc. This is discussed more here: Can you only inject services into services through bootstrap?

Community
  • 1
  • 1
Mark Rajcok
  • 348,511
  • 112
  • 482
  • 482
4

Service is considered to be shared among components. So let's say if I have one service, I can use it in different components.

Here In this answer I'm showing you one service which accepts data from one component and sends that data to other component.

I have used concept of Routing, Shared-Service, Shared-Object. I hope this will help you to understand the basic of share-service.

Note: @Injectable decorater is used to make service 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)
    ]);

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 and shared Object

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;
  }
} 
micronyks
  • 49,594
  • 15
  • 97
  • 129
3

not sure if an answer is still required so i would go ahead and try to answer this.

Consider the following example where we have a Component which uses a service to populate some values in its template like below

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

The above code is pretty simple and it will try to fetch whatever getPersons return from the DataService. The DataService file is available below.

data.service.ts

export class DataService {

persons:<Array>;

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

getPersons(){

return this.persons

}

The above piece of code will work perfectly fine without the use of the @Injectable decorator. But the problem will start when our service(DataService in this case) requires some dependencies like for eg. Http. if we change our data.service.ts file as below we will get an error saying 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

}

This has something to do with the way decorators function in Angular 2. Please read https://blog.thoughtram.io/angular/2015/05/03/the-difference-between-annotations-and-decorators.html to get an in depth understanding of this issue.

The above code will also not work as we have to import HTTP in our bootstrap module as well.

But a thumb rule i can suggest is that if your service file needs a dependency then you should decorate that class with a decorator @Injectable.

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

Apoorv
  • 587
  • 7
  • 19
2

Somehow @Injectable doesn't work for me in Angular 2.0.0-beta.17 when wiring ComponentA -> ServiceB -> ServiceC.

I took this approach:

  1. Reference all services in the @ComponentA's providers field.
  2. In ServiceB use the @Inject annotation in the constructor to wire ServiceC.

Run this Plunker to see an example or view code below

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");
    }
}
Julius
  • 2,606
  • 6
  • 28
  • 51
  • Using `@Inject(...)` is redundant if the type of the constructor parameter is the same as the one passed to `@Inject(...)` and the class has the `@Injectable()` (with `()`) annotation. – Günter Zöchbauer May 22 '16 at 14:49
  • I get "Cannot resolve all parameters for OverviewService(?)" when I try that. Check https://plnkr.co/edit/g924s5KQ0wJW83Qiwu0e?p=preview – Julius May 22 '16 at 15:31
0

The first thing to do is to annotate all services with the @Injectable annotation. Notice the parentheses at the end of the annotation, without this this solution won't work.

Once this is done, we can then inject services into each other using constructor injection:

@Injectable()
export class MyFirstSvc {

}

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

@Injectable()
export class MyThirdSvc {
    constructor(helpfulService: MyFirstSvc) {        
    }
}
Angular University
  • 38,399
  • 15
  • 70
  • 79
0

First You need to provide your service

You could provide it either in the bootstrap method:

bootstrap(AppComponent,[MyFirstSvc]);

or the on the app component, or in any other component, depending on your needs.:

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

then just inject you service using the constructor :

export class MySecondSvc {
      constructor(private myFirstSvc : MyFirstSvc ){}
}
bougsid
  • 94
  • 1
  • 4