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.com/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: 'mycompany',
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?
In short since injectors are defined at component level, the component 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 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/>
Get data
`,
(...)
})
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.
Surely you do.
In Angular2, there are multiple injectors. Injectors are configured using the providers array of components. When a component has a providers array, an injector is created at that point in the tree. When components, 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 component tree, but it is sparser than the component 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 component tree to do this. So even though it feels like we are configuring dependencies on the component tree, I like to think that I am configuring dependencies on the injector tree (which happens to overlay the component tree, so I have to use the components 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 component.
Architect your app, then go back and configure the injector tree (using components). That's how I like to think of it. If you reuse components 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 component that uses them:
#Component({providers: [service1, service2]})
#Injectable decorator adds necessary metadata to track dependecies, but does not provide them.
Related
Background
I am using inheritance in Angular, the major issue was that I initially had to pass services from parent to child, something like
// Parent
export class ParentComponent {
protected myService: MyService;
constructor(private myService: MyService) {
}
}
// Child
export class ChildComponent extends ParentComponent {
constructor(private myService: MyService) {
super(myService);
}
}
To avoid this I found a different approach using Injector from #angular/core. In this approach we follow the below steps
Create a service
app-injector-service.ts
import { Injector } from '#angular/core';
export class AppInjectorService {
private static injector: Injector;
static setInjector(injector: Injector) {
AppInjectorService.injector = injector;
}
static getInjector(): Injector {
return AppInjectorService.injector;
}
}
in the main.ts file
platformBrowserDynamic()
.bootstrapModule(AppModule)
.then((ref) => {
AppInjectorService.setInjector(ref.injector);
})
.catch((err) => console.error(err));
and parent class file would look like
export abstract class ParentComponent {
protected myService: MyService;
constructor() {
const injector = AppInjectorService.getInjector();
this.myService = injector.get(MyService);
}
}
With this approach, no need to pass service to the super call! Amazing
My Issue
The approach above worked very well untill I needed to extract the route param from the activated route so I followed the same structure. To my surprise It is not working. I tested with manually injecting the ActivatedRoute in the child class and the parameter exists. I dont understand why the parameter is not available in the class when we inject ActivatedRoute using the AppInjector. Ofcourse I can go back to injecting it in the child class then passing it to the super function so that it can be accessed by the Parent class but this is what I was trying to avoid...
Below is a stackblitz demo showing the issue, click on the route and you will notice that we get back null from the ActivatedRoute injected in the parent class but a value in the ActivatedRoute in the child class. Basically I am trying to find if there is anything am doing wrong or is there something am missing while using the Injector
Demo
There are two problems in your code: using inheritance for components and using a non-singleton service as a singleton service.
you are setting up injector in your service AppInjectorService.setInjector(ref.injector); after app is bootstrapped but what happens when one of your component is directly used in your root component(AppComponent) and that component inherited that parent comp class, as you are using injector inside parent to resolve all required dependencies, it won't work because you do not have access to injector by that time, setting up injector in constructor using that static method is a bad idea because constructors of components can be invoked even though they are not being rendered yet and they also may be called during some bootstrapping process so you see the problem? your parent component's constructor is called before AppInjectorService.setInjector(ref.injector);. This may have worked if all the components inheriting your parent component were initialized lazily/dynamically so that app is bootstraped and your injector is setup in your service before using it.
Inheritance is a bitch, especially with components it'll bite you in the ass sooner or later, use DI to reduce repetitive code, in your case you can create a service that can contain all the required dependencies and inject it into whatever component you want to, the lines of code to inherit a component vs injecting a service are typically the same though this comes with the limitation that you can't mix and match services of different scopes(Singletons/Module level/Component level) but that is expected to be honest.
Second problem is using a contextual service out of context, ActivatedRoute is meant to be injected into components that are part of route config, let's takte example of this config:
{ path: ":id", component: ChildComponent }
if you inject ActivatedRoute into ChildComponent, it'll work as expected but if you inject it into AppComponent, it won't work it's not gonna throw any errors but when you'll try to listen to changes in paramMap, you won't get it because it AppComponent is not connected to that specific path. If you want to globally listen to changes in route then prefer Router.
One thing to note here, when you pass that route config to RouterModule, it'll now be responsible for creating instances of components based on route path and it can then inject an injector with a newly created dependency(like ActivatedRoute) into that component so injecting dependencies like those anywhere may not work.
I'm creating a #Log() Decorator Function for debugging purposes;
I want that Decorator to delegate some of it's logic to a LoggingService that in turn depends on other services from the app...
I've been trying a lot of different things, the simplest/most straightforward way was to cache the Main (or Shared) Module's Injector as a static prop on the module itself (see StackBlitz example linked below), and that works for lazy-loaded modules, but not for eagerly loaded ones...
Non-working poc: https://stackblitz.com/edit/angular-j1bpvx?file=app%2Fdecorator.ts
Is there a way I could mkae use of that Service in there??
Thanks!
Class decorator is executed once on class definition. In order to avoid race condition when calling AppModule.injector.get(LoggingService) it should be moved to the place where AppModule.injector is already defined, i.e. class method.
It should be:
constructor.prototype[hook] = function (args) {
const loggingService = AppModule.injector.get(LoggingService);
loggingService.log({ ... })
...
This also creates tight coupling with AppModule and prevents the units from being reused or tested separately from it. It's recommended to use another object to hold injector property, e.g. assign injector not in main but in child module that is imported into AppModule:
export class InjectorContainerModule {
static injector: Injector;
constructor(injector: Injector) {
InjectorContainerModule.injector = injector;
}
}
Try stackblitz fixed
This will print
LoggingService: HelloComponent - ngOnInit was called
Minor changes - basically using ReflectiveInjector as in angular Injector#example
import { ReflectiveInjector } from '#angular/core';
const injector = ReflectiveInjector.resolveAndCreate([
{provide: 'loggingService', useClass: LoggingService}
]);
const loggingService = injector.get('loggingService');
I am sure you can use useExisting and use LoggingService as provider in your app module.
I'm using angular. I already know that when an appmodule is importing modules which declares providers, the root injector gets them all and the service is visible to the app - globally. (I'm not talking about lazy loaded modules)
But is it possible that each module will have its own instance of the service?
I thought of maybe something like this :
#NgModule({
providers: [AService]
})
class A {
forRoot() {
return {
ngModule: A,
providers: [AService]
}
}
forChild() {
return {
ngModule: A,
providers: [AService]
}
}
}
But I don't know if it's the right way of doing it
Question
How can I accomplish service per module ?
STACKBLITZ : from my testing , they are using the same service instance
When we provide a service in a feature module that is eagerly loaded
by our app's root module, it is available for everyone to inject. - John Papa
So looks like there is no way to inject at feature-module level, as there are no module level injectors other than the one at root module.
But angular has nodes at each component level for the injector, so such a scenario will have to use coponent level-injectors I guess.
You can also have a parent component inject the service for different children sharing the same instance.
One way is to provide the services at component level. Not sure if that will work for you.
Also, check the multiple edit scenario in the docs
https://angular-iayenb.stackblitz.io
import { Component, OnInit } from '#angular/core';
import {CounterService} from "../../counter.service"
#Component({
selector: 'c2',
templateUrl: './c2.component.html',
styleUrls: ['./c2.component.css'],
providers:[CounterService]
})
export class C2Component implements OnInit {
constructor(private s:CounterService) { }
ngOnInit() {
}
}
Question
How can I accomplish service per module ?
Not with the default Injector. Default Injector keeps nodes at root level and component level, not at feature-module level. You will have to have a custom Injector if there is a real scenario.
Edit: the previous answer from me is not completely correct. The correct way to make this work is to use lazy loaded modules, provide the services there. The given service should not be provided with a static forRoot() method somewhere because then the lazy loaded module will access the root injector.
There is no actual reference to do so because that is not how angular is designed but if you want it that way you have to the opposite of
https://angular.io/guide/singleton-services#providing-a-singleton-service
and
https://angular.io/guide/singleton-services#forroot
Old not completely correct:
You simple have to declare the service you want to be single instance for each and every module within the providers meta data of each and every module. Then Angular will not reuse any instance of the service.
Giving scenario: You have two modules, ModuleA and ModuleB, both need the service but different instance, then you will declare them in the providers section of ModuleA and ModuleB.
Reference:
https://angular.io/guide/singleton-services
I have built a shared data service that's designed to hold the users login details which can then be used to display the username on the header, but I cant get it to work.
Here's my (abbreviated) code:
// Shared Service
#Injectable()
export class SharedDataService {
// Observable string source
private dataSource = new Subject<any>();
// Observable string stream
data$ = this.dataSource.asObservable();
// Service message commands
insertData(data: Object) {
this.dataSource.next(data)
}
}
...
// Login component
import { SharedDataService } from 'shared-data.service';
#Component({
providers: [SharedDataService]
})
export class loginComponent {
constructor(private sharedData: SharedDataService) {}
onLoginSubmit() {
// Login stuff
this.authService.login(loginInfo).subscribe(data => {
this.sharedData.insertData({'name':'TEST'});
}
}
}
...
// Header component
import { SharedDataService } from 'shared-data.service';
#Component({
providers: [SharedDataService]
})
export class headerComponent implements OnInit {
greeting: string;
constructor(private sharedData: SharedDataService) {}
ngOnInit() {
this.sharedData.data$.subscribe(data => {
console.log('onInit',data)
this.greeting = data.name
});
}
}
I can add a console log in the service insertData() method which shoes the model being updated, but the OnInit method doesn't reflect the change.
The code I've written is very much inspired by this plunkr which does work, so I am at a loss as to what's wrong.
Before posting here I tried a few other attempts. This one and this one again both work on the demo, but not in my app.
I'm using Angular 2.4.8.
Looking through different tutorials and forum posts all show similar examples of how to get a shared service working, so I guess I am doing something wrong. I'm fairly new to building with Angular 2 coming from an AngularJS background and this is the first thing that has me truly stuck.
Thanks
This seems to be a recurring problem in understanding Angular's dependency injection.
The basic issue is in how you are configuring the providers of your service.
The short version:
Always configure your providers at the NgModule level UNLESS you want a separate instance for a specific component. Only then do you add it to the providers array of the component that you want the separate instance of.
The long version:
Angular's new dependency injection system allows for you to have multiple instances of services if you so which (which is in contrast to AngularJS i.e. Angular 1 which ONLY allowed singletons). If you configure the provider for your service at the NgModule level, you'll get a singleton of your service that is shared by all components/services etc. But, if you configure a component to also have a provider, then that component (and all its subcomponents) will get a different instance of the service that they can all share. This option allows for some powerful options if you so require.
That's the basic model. It, is of course, not quite so simple, but that basic rule of configuring your providers at the NgModule level by default unless you explicitly want a different instance for a specific component will carry you far.
And when you want to dive deeper, check out the official Angular docs
Also note that lazy loading complicates this basic rule as well, so again, check the docs.
EDIT:
So for your specific situation,
#Component({
providers: [SharedDataService] <--- remove this line from both of your components, and add that line to your NgModule configuration instead
})
Add it in #NgModule.providers array of your AppModule:
if you add it in #Component.providers array then you are limiting the scope of SharedDataService instance to that component and its children.
in other words each component has its own injector which means that headerComponentwill make its own instance of SharedDataServiceand loginComponent will make its own instance.
My case is that I forget to configure my imports to add HttpClientModule in #NgModules, it works.
I have the below service:
import {Injectable} from 'angular2/core';
import {Service2} from './app.service2';
#Injectable()
export class Service1 {
constructor(service2:Service2) {
this.service2 = service2;
}
getData() {
return this.service2.getData();
}
}
I am using this service in one of my component, but i am not sure how does this service recieve the service2 as argument? Tradionally, in JS i do something like this.
var s = new Service1(service2);
But i don't see anything like this in below plunker, yet it works.
https://plnkr.co/edit/PsySVcX6OKtD3A9TuAEw?p=preview
Can anyone add some light on this.
Angular2 uses dependency injection (DI) to create instances. It creates class instances for components, directives, pipes, and services for you. For this it checks the constructor parameters and its registered list of providers for matchin instances and when DI calls new Xxx(...) for you it passes all providers found in its providers list and one was not found it will throw an exception.
If one of the classes has an constructor with parameters, the class needs to have a decorator (one of #Component(), #Directive(), #Pipe(), or #Injectable(). This indicates for DI that it needs to analyze the constructor.