Angular Providers - javascript

Following the angular 2 tutorial # https://angular.io/docs/ts/latest/tutorial/toh-pt4.html
In the
#Component({
})
I inserted "providers: [HeroService]" which contains the getHeroes() method.
Created a constructor:
constructor(private heroService: HeroService) {}
Now the part I don't understand is how I am able to use
this.heroService.getHeroes()
The only propertes defined in this class are:
title = 'Tour of Heroes';
heroes: Hero[];
selectedHero: Hero;
Does the providers in the #Component decorator automatically create a property to access it through this.?
The App is working, just don't know how we magically were able to access heroService through this.

The private (could also be public) in
constructor(private heroService: HeroService) {}
also creates a property heroService and assigns the value passed to the constructor. That's a TypeScript feature and is not Angular2 or DI dependent.

Related

How can I change the name of the template that is passed to the #Render decorator in the Interceptor?

How can I change the name of the template that is passed to the #Render decorator in the Interceptor? I need to add the desired locale to the template url, depending on the user's language. I'm trying to do this, but it doesn't work. How can I do this?
#Injectable()
export class RenderByLanguageInterceptor implements NestInterceptor {
constructor(private reflector: Reflector) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const template = this.reflector.getAllAndOverride<string>(RENDER_METADATA, [
context.getHandler(),
context.getClass(),
]);
const lang = context.switchToHttp().getRequest().i18nLang;
SetMetadata(RENDER_METADATA, `${lang}/${template}`);
return next.handle();
}
}
SetMetadata is a decorator from Nest's library as a helper function. What you're needing to do is use the Reflect API and do something along the lines of
Reflect.defineMetadata(
RENDER_METADATA,
`${lang}/${tempalte}`,
context.getClass(),
context.getHandler()
);
Where Reflect is from reflect-metadata. This should set the metadata appropriately, so it can be read later. With this approach, you will have to make sure the metadata is set every time, as there's no falling back to the original metadata in the #Render() decorator.

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 Injectror

I have a few components; I'm using Injector in constructor for encapsulation
import { Component, Injector, OnInit } from '#angular/core';
#Component({
selector: 'app-base',
templateUrl: './base.component.html',
styleUrls: ['./base.component.css'],
})
export class BaseComponent implements OnInit {
some = '';
constructor(injector: Injector) {
this.some = injector.get(this.some);
}
ngOnInit(): void {}
}
I'm using BaseComponent in other Component
import { BaseComponent } from '../base/base.component';
#Component({
selector: 'app-base-state',
templateUrl: './base-state.component.html',
styleUrls: ['./base-state.component.css'],
})
export class BaseStateComponent extends BaseComponent implements OnInit {
constructor(injector: Injector) {
super(injector);
}
ngOnInit(): void {}
}
BaseStateComponent I'm going to use in others component; Question is:
Is there any way, to make injector in BaseComponent or BaseSateComponent Optional;
I have a case, when I need a component, but I don't need an injector of it;
I know about feature
constructor(#Optional(), #Self() etc...);
But truly to say, I can't understand how it work's; I will be grateful if somebody can explain it Decorators;
stack
The problem is that you want to use #Optional #Self to make injector optional. But, #optional #self works well with something that is injectable which you can provide in providers array at some level. In Angular, services are injectable. So you can use #Optional #Self #SkipSelf with services or something injectable
When you use constrocutor(private someService:SomeService), Angular will check whether SomeService is provided at componoent level means in #component's providers, if not, is it provided at Module Level, if not , is it provided at Root level? This way angular checks entire Injector tree.
When you use #Optional, it will not perform this check and so on....
Keep in mind that Injector resolves a token into a dependency.
Answer to your question
You can use optional parameter approach in typescript by simply providing ? mark next to parameter as shown below,
export class BaseComponent implements OnInit {
some = '';
constructor(private injector?: Injector) { // ? to make parameter optional
this.some = injector.get(this.some);
}
ngOnInit(): void {}
}
it also makes sense because you are using OOPs over classes.
Forked Stackblitz (Fixed some other issues also)

When is #Inject optional in Angular?

I have a simple service in Angular
// service.ts
export class SimpleService {
// ...
}
// component.ts
#Component({
selector: 'my-component',
templateUrl: 'components/mycomp/mycomp.html',
providers: [
SimpleService
]
})
class MyComponent {
constructor(private ss: SimpleService) {}
}
This above code never works. I get an error: Uncaught Error: Can't resolve all parameters for MyComponent: (?).
However, if I change my constructor definition to:
class MyComponent {
constructor(#Inject(SimpleService) private ss: SimpleService) {}
}
Then it works. Even documentation doesn't seem to use #Inject. From the documentation I understand that #Inject is explicitly required when my provider token is not class; like, trying to inject primitive values, or using opaque tokens, etc.
Also, I am confused in regards to typescript. Official documentation clearly mentions that when #Inject() is not present, Injector will use the type annotation of the parameter. If type information is erased during typescript transpilation and DI is happening when app is running, how can Angular use type annotation at runtime? Is there anything I am missing?
You forgot to add
"emitDecoratorMetadata": true
to your tsconfig.json
See also
angularjs 2.0: Can't inject anything through component constructor()

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