What is the need for #Inject and Injectable in Angular DI? - javascript

constructor(private smartphoneService: smartphoneService) { }
I can run above code without an error. But why do we use #Inject and Injectable on services, Pipes and etc..Any handful use..? why and where we need to use this..??
import { Injectable } from '#angular/core';
import {SmartphoneListComponent} from './smartphone-list/smartphone-list.component';
#Injectable()
export class smartphoneService{
smartphones: any = [
{id: '1', name: 'iPhone6', status: 'active'},
{id: '2', name: 'iPhone6S', status: 'active'},
{id: '3', name: 'iPhone7', status: 'active'},
{id: '4', name: 'iPhone7Plus', status: 'active'},
{id: '5', name: 'iPhoneX', status: 'future'}
];
constructor() { }
}

The Angular documentation provides the answer to your question:
#Injectable() marks a class as available to an injector for instantiation. Generally speaking, an injector reports an error when trying to instantiate a class that is not marked as #Injectable().
Besides that, they recommend to put the annotation on every service class for the following reasons:
Future proofing: No need to remember #Injectable() when you add a dependency later.
Consistency: All services follow the same rules, and you don't have to wonder why a decorator is missing.
Here is the link to the documentation:
https://angular.io/guide/dependency-injection#why-injectable

#Injectable
You need to use #Injectable on services that inject dependencies and will be injected using class type:
#Injectable()
class A { constructor (b: B) {} }
class C { constructor (c: C) {} }
You don't need to use #Injectable if a service doesn't inject dependencies:
class A { constructor () {} }
class C { constructor (c: C) {} }
Or it's being injected using #Inject decorator:
class A { constructor (b: B) {} }
class C { constructor (#Inject(C) c) {} }
Basically, you can use any decorator instead of #Injectable. This will work:
function Custom(target) {}
#Custom
class A { constructor (b: B) {} }
class C { constructor (c: C) {} }
This is because when Angular tries to understand what injectables to pass to a class constructor, it uses metadata of a class. And this metadata is generated by TypeScript only if you have emitDecoratorMetadata: true in tsconfig.json and any decorator is applied to a class.
#Inject
Angular doesn't use metadata generated by TypeScript when you use #Inject decorator explicitly, that's why you don't need to apply #Injectable if you use #Inject. The most common use case of #Inject is to inject object by reference, not type:
const token1 = new InjectionToken('my');
const token2 = 's';
class C { constructor (#Inject(token1) t1, #Inject(token2) t2) {} }

A Component is instantiated by Angular and it is already decorated by #Component. Adding #Inject is implied. So why force developers to use #Inject for Components?
OTOH if Angular required #Inject for it's constructor then what does it mean to have a constructor arg that is not injected? It becomes pointless arg since it is not injected and Angular has to create instances? Then we would have to enforce that every argument is marked #inject.
Services look like first class JavaScript classes. So are domain entity classes. However services don't carry the state and so it is pointless to have several instances hanging around of same service class. OTOH entities can be multiple instances of same class.
Now since these 2 types of classes look alike how do we tell Angular which class is Singleton and most importantly how do we tell Angular that we need an instance injected somewhere.
In DI we are saying I want an instance of a class.

Related

What is the purpose of a `getInstance()` method?

I am looking at a user service, my understanding is it's similar to a user service in Nest, but not really.
In it I see the following:
export class UsersService {
private usersDao: UsersDao
constructor() {
this.usersDao = UsersDao.getInstance();
}
}
static getInstance(): UsersService {
if (!UsersService.instance) {
UsersService.instance = new UsersService();
}
return UsersService.instance;
}
What is that getInstance() doing exactly? And why not just:
export class UsersService {
constructor(private usersDao: UsersDao) {}
}
What is the goal of getInstance()?
Usually this is part of the singleton pattern. Basically one class that, once instantiated, any subsequent classes will refer to that instance, rather than creating a fresh instance each time.
https://en.wikipedia.org/wiki/Singleton_pattern
Its useful for a class where something complex needs to happen when it is first constructed, but all following calls just need access to the properties.
I'd also like to mention that you can (in JavaScript specifically) export an instance, and all modules that import the module will have access to the same instance.

Angular: manually instantiate class with dependency injection

I am using Angular 10.0 and I have a problem with --prod compiling.
I need to instantiate classes manually and need to support dependency injection.
The following code works fine during development to instantiate my classes:
public instantiateWithDi(parentInjector: Injector, myClass: any): any {
const reflector = ReflectiveInjector.resolveAndCreate([], parentInjector);
const newInstance = reflector.resolveAndInstantiate(myClass);
return newInstance;
}
When I build my project with --prod (or --optimization=true), then I get the following error at runtime:
ERROR Error: Cannot resolve all parameters for 'e'(?). Make sure that all the parameters are decorated with Inject or have valid type annotations and that 'e' is decorated with Injectable.
Decorating the constructor parameters of the classes with #Inject did not work either. Using injection tokens does not help as well.
The classes are already decorated with #Injectable() and in the "providers" array of their respective angular module.
I know, the ReflectiveInjector is deprecated, but simply using the get method of the injector does not work either, because it seems to cache the classes once created and does not re-instantiate them each time I call my "instantiateWithDi" method.
Example usage
I've created a small demo at stackbliz for this: https://stackblitz.com/edit/angular-plugin-mechanism?file=src/app/plugin-execution.service.ts
Basically the magic happens here (plugin-execution.service.ts):
#Injectable({
providedIn: 'root'
})
export class PluginExecutionService {
public static readonly eventListeners = [];
constructor(private injector: Injector){}
private instantiateWithDi(parentInjector: Injector, myClass: any): any {
const reflector = ReflectiveInjector.resolveAndCreate([], parentInjector);
const newInstance = reflector.resolveAndInstantiate(myClass);
return newInstance;
}
public onApplicationEvent(event: ApplicationEvent){
const injector = Injector.create({
parent: this.injector,
providers: [{
provide: ApplicationEvent,
useValue: event
}]
});
PluginExecutionService
.eventListeners
.forEach(cls => this.instantiateWithDi(injector, cls));
}
}
This allows developers to create a class and push their class into a eventListener array. It gets executed every time, an application event occurs.
See the example "plugin" some.plugin.ts in the stackblitz example.
The real usecase is of course much more complex and involves custom decorators and stuff, but that would be quite an overkill for a demo.
You see the result in the console. The "plugins" work fine as intended. But when i build it using --prod, the app does not work any longer...
Any help is very much appreciated!
Thanks,
Manuel

Angular 5: Using Service from inside Custom Decorator Function

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.

Angular 2 - service - dependency injection from another service

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.

Dependency injection in Angular 2 when a constructor has arguments

I have a typescript class representing a model and I would like instances to communicate with an API via angular's Http service.
But the constructor of the model needs arguments when creating instances. For example something super simple:
class SomeModel{
constructor(public id:number, public name:string, ){
}
I would like to inject the Http service so it is available to my instances, but it seems like the canonical way to do this commandeers the constructor with:
constructor(http:Http)
I've been digging through the Injector docs, but it's a little sparse and I haven't found anything that works. Is there a way to get a reference to a service like Http from the DI system without using the constructor pattern?
I managed to solve the same problem using angular 4. First you create new injector that uses component injector. It knows about your SomeModel class and passes modelParams as instance of SomeModelParameters class. Then you use this newly created injector to create class instance.
#Injectable()
class SomeModel {
constructor(http: Http, someModelParamters: SomeModelParameters) { }
}
export class MyComponent {
constructor(injector: Injector) {
const modelParams = new SomeModelParameters();
const injectorWithModelParams = ReflectiveInjector.resolveAndCreate(
[
SomeModel,
{ provide: SomeModelParameters, useValue: modelParams }
],
injector);
this.someModel = injectorWithModelParams.resolveAndInstantiate([SomeModel]);
}
}
update
HTTP_PROVIDERS is long gone.
HttpClientModule is the current replacement.
original
If you inject a class that has constructor parameters the #Injectable annotation needs to be added.
#Injectable()
class SomeModel{
// constructor(public id:number, public name:string, ){
// }
constructor(http:Http) {}
}
For this to work HTTP_PROVIDERS needs to be added to bootstrap(AppComponent, [HTTP_PROVIDERS]);
See also Angular2 beta - bootstrapping HTTP_PROVIDERS - "Unexpected Token <"
If you need to pass other arguments from your component, youcoud pass them using a function instead.
Another way is to create the instance manually and request Http from the injector.
export class MyComponent {
constructor(injector: Injector) {
this.someModel = new SomeModel(Injector.resolveAndCreate(Http), 1, 2);
}
}

Categories