I have written two services in Angular 2. One of those is a basic, customised class of Http
with some custom functionality in (it looks basic for now, but it will be expanding):
ServerComms.ts
import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';
@Injectable()
export class ServerComms {
private url = 'myservice/service/';
constructor (public http: Http) {
// do nothing
}
get(options) {
var req = http.get('https://' + options.name + url);
if (options.timout) {
req.timeout(options.timeout);
}
return req;
}
}
Another class, TicketService
utilises this class above, and calls one of the methods in the service. This is defined below:
TicketService.ts
import {Injectable} from 'angular2/core';
import {ServerComms} from './ServerComms';
@Injectable()
export class TicketService {
constructor (private serverComms: ServerComms) {
// do nothing
}
getTickets() {
serverComms.get({
name: 'mypany',
timeout: 15000
})
.subscribe(data => console.log(data));
}
}
However, I receive the following error whenever I try this:
"No provider for ServerComms! (App -> TicketService -> ServerComms)"
I do not understand why? Surely I do not need to inject every service that each other service relies upon? This can grow pretty tedious? This was achievable in Angular 1.x - how do I achieve the same in Angular 2?
Is this the right way to do it?
I have written two services in Angular 2. One of those is a basic, customised class of Http
with some custom functionality in (it looks basic for now, but it will be expanding):
ServerComms.ts
import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';
@Injectable()
export class ServerComms {
private url = 'myservice./service/';
constructor (public http: Http) {
// do nothing
}
get(options) {
var req = http.get('https://' + options.name + url);
if (options.timout) {
req.timeout(options.timeout);
}
return req;
}
}
Another class, TicketService
utilises this class above, and calls one of the methods in the service. This is defined below:
TicketService.ts
import {Injectable} from 'angular2/core';
import {ServerComms} from './ServerComms';
@Injectable()
export class TicketService {
constructor (private serverComms: ServerComms) {
// do nothing
}
getTickets() {
serverComms.get({
name: 'mypany',
timeout: 15000
})
.subscribe(data => console.log(data));
}
}
However, I receive the following error whenever I try this:
"No provider for ServerComms! (App -> TicketService -> ServerComms)"
I do not understand why? Surely I do not need to inject every service that each other service relies upon? This can grow pretty tedious? This was achievable in Angular 1.x - how do I achieve the same in Angular 2?
Is this the right way to do it?
Share Improve this question asked Apr 15, 2016 at 15:55 keldarkeldar 6,26211 gold badges54 silver badges84 bronze badges3 Answers
Reset to default 4In short since injectors are defined at ponent level, the ponent that initiates the call services must see the corresponding providers. The first one (directly called) but also the other indirectly called (called by the previous service).
Let's take a sample. I have the following application:
Component
AppComponent
: the main ponent of my application that is provided when creating the Angular2 application in thebootstrap
function@Component({ selector: 'my-app', template: ` <child></child> `, (...) directives: [ ChildComponent ] }) export class AppComponent { }
Component
ChildComponent
: a sub ponent that will be used within theAppComponent
ponent@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
andService2
:Service1
is used by theChildComponent
andService2
byService1
@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 theproviders
attribute of this ponent. 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 theAppComponent
one. To inject elements involved in the injection chain executed forr the ponent, providers will be looked for first in this injector, then in theAppComponent
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 ponents 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 ponents, all services, ...).
- If you define a provider at a ponent level, the instance will be shared by the ponent itself, its sub ponents 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.
Surely you do.
In Angular2, there are multiple injectors. Injectors are configured using the providers
array of ponents. When a ponent has a providers
array, an injector is created at that point in the tree. When ponents, directives, and services need to resolve their dependencies, they look up the injector tree to find them. So, we need to configure that tree with providers.
Conceptually, I like to think that there is an injector tree that overlays the ponent tree, but it is sparser than the ponent tree.
Again, conceptually, we have to configure this injector tree so that dependencies are "provided" at the appropriate places in the tree. Instead of creating a separate tree, Angular 2 reuses the ponent tree to do this. So even though it feels like we are configuring dependencies on the ponent tree, I like to think that I am configuring dependencies on the injector tree (which happens to overlay the ponent tree, so I have to use the ponents to configure it).
Clear as mud?
The reason Angular two has an injector tree is to allow for non-singleton services – i.e., the ability to create multiple instances of a particular service at different points in the injector tree. If you want Angular 1-like functionality (only singleton services), provide all of your services in your root ponent.
Architect your app, then go back and configure the injector tree (using ponents). That's how I like to think of it. If you reuse ponents in another project, it is very likely that the providers
arrays will need to be changed, because the new project may require a different injector tree.
Well, i guess you should provide both services globally:
bootstrap(App, [service1, service2]);
or provide to ponent that uses them:
@Component({providers: [service1, service2]})
@Injectable
decorator adds necessary metadata to track dependecies, but does not provide them.