When is #Inject optional in Angular? - javascript

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

Related

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

Why can't you add a parameter to the constructor when using #Component decorator?

When I try to add a parameter to the constructor of a class I have an error: "Can't resolve all paremeters for Test c /: Users /....../ test.component.ts :(?).
import {Component} from "#angular/core";
#Component({
selector:"app-rueba",
template: "hola"
})
export class Prueba
{
n:string;
constructor(nombre:string)
{
this.n=nombre;
}
}
Why is this happening?? Excuse me if the answer is pretty obvious I'm pretty new to angular
I do not see any need of having parameters in the constructor. You are not creating objects from the component like:
const object = new MyComponent(param);
In Angular, the constructor is used for injecting dependencies. The parameters are dependency injections. There is a difference between class and component. Read more here:
https://angular.io/guide/dependency-injection

Inject a provider in another provider, same module #1250

I have a service/provider, let's say it's call ServiceA1, in a module A marked as #Injectable().
In the same module I have another service/provider, let's say it's call ServiceA2, marked as #Injectable().
I want to inject ServiceA1 in ServiceA2 I try with:
This code throw an error: serviceA1 undefined.
My module.ts
Do I have to make another module to use serviceA2 in serviceA1?
If you're injecting a service directly by the class, you do not need the #Inject():
#Injectable()
export class ServiceA2 {
constructor(private serviceA1: ServiceA1) {
}
This is the same as:
#Injectable()
export class ServiceA2 {
constructor(#Inject(ServiceA1) private serviceA1: ServiceA1) {
}
Note that you injected #Inject('ServiceA1') instead of #Inject(ServiceA1). This is matching by a string token instead of a class and can therefore not be resolved.
If you wanted to inject the class by a string token, you would need to declare the provider in your module like this:
providers: [
ServiceA2,
{provide: 'ServiceA1', useClass: ServiceA1},
]
This also works within the same module.
Anyone running into this: make sure you remember to annotate the injected service with #Injectible.

What is ngDefaultControl in Angular?

No, this is not a duplicate question. You see, there is a ton of questions and issues in SO and Github that prescribe that I add this directive to a tag that has [(ngModel)] directive and is not contained in a form. If I don't add it I get an error:
ERROR Error: No value accessor for form control with unspecified name attribute
Ok, the error goes away if I put this attribute there. BUT, wait! Nobody knows what it does! And Angular's doc doesn't mention it at all. Why do I need a value accessor when I know that I don't need it? How is this attribute connected to value accessors? What does this directive do? What is a value accessor and how do I use it?
And why does everybody keep doing things that they don't understand at all? Just add this line of code and it works, thank you, this is not the way to write good programs.
And then. I read not one but two huge guides about forms in Angular and a section about ngModel:
https://angular.io/guide/forms
https://angular.io/guide/reactive-forms
https://angular.io/guide/template-syntax#ngModel
And you know what? Not a single mention of either value accessors or ngDefaultControl. Where is it?
[ngDefaultControl]
Third party controls require a ControlValueAccessor to function with angular forms. Many of them, like Polymer's <paper-input>, behave like the <input> native element and thus can use the DefaultValueAccessor. Adding an ngDefaultControl attribute will allow them to use that directive.
<paper-input ngDefaultControl [(ngModel)]="value>
or
<paper-input ngDefaultControl formControlName="name">
So this is the main reason why this attribute was introduced.
It was called ng-default-control attribute in alpha versions of angular2.
So ngDefaultControl is one of selectors for DefaultValueAccessor directive:
#Directive({
selector:
'input:not([type=checkbox])[formControlName],
textarea[formControlName],
input:not([type=checkbox])[formControl],
textarea[formControl],
input:not([type=checkbox])[ngModel],
textarea[ngModel],
[ngDefaultControl]', <------------------------------- this selector
...
})
export class DefaultValueAccessor implements ControlValueAccessor {
What does it mean?
It means that we can apply this attribute to element (like polymer component) that doesn't have its own value accessor. So this element will take behaviour from DefaultValueAccessor and we can use this element with angular forms.
Otherwise you have to provide your own implementation of ControlValueAccessor
ControlValueAccessor
Angular docs states
A ControlValueAccessor acts as a bridge between the Angular forms API
and a native element in the DOM.
Let's write the following template in simple angular2 application:
<input type="text" [(ngModel)]="userName">
To understand how our input above will behave we need to know which directives are applied to this element. Here angular gives out some hint with the error:
Unhandled Promise rejection: Template parse errors: Can't bind to
'ngModel' since it isn't a known property of 'input'.
Okay, we can open SO and get the answer: import FormsModule to your #NgModule:
#NgModule({
imports: [
...,
FormsModule
]
})
export AppModule {}
We imported it and all works as intended. But what's going on under the hood?
FormsModule exports for us the following directives:
#NgModule({
...
exports: [InternalFormsSharedModule, TEMPLATE_DRIVEN_DIRECTIVES]
})
export class FormsModule {}
After some investigation we can discover that three directives will be applied to our input
NgControlStatus
#Directive({
selector: '[formControlName],[ngModel],[formControl]',
...
})
export class NgControlStatus extends AbstractControlStatus {
...
}
NgModel
#Directive({
selector: '[ngModel]:not([formControlName]):not([formControl])',
providers: [formControlBinding],
exportAs: 'ngModel'
})
export class NgModel extends NgControl implements OnChanges,
DEFAULT_VALUE_ACCESSOR
#Directive({
selector:
`input:not([type=checkbox])[formControlName],
textarea[formControlName],
input:not([type=checkbox])formControl],
textarea[formControl],
input:not([type=checkbox])[ngModel],
textarea[ngModel],[ngDefaultControl]',
,,,
})
export class DefaultValueAccessor implements ControlValueAccessor {
NgControlStatus directive just manipulates classes like ng-valid, ng-touched, ng-dirty and we can omit it here.
DefaultValueAccesstor provides NG_VALUE_ACCESSOR token in providers array:
export const DEFAULT_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DefaultValueAccessor),
multi: true
};
...
#Directive({
...
providers: [DEFAULT_VALUE_ACCESSOR]
})
export class DefaultValueAccessor implements ControlValueAccessor {
NgModel directive injects in constructor NG_VALUE_ACCESSOR token that was declared on the same host element.
export NgModel extends NgControl implements OnChanges, OnDestroy {
constructor(...
#Optional() #Self() #Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
In our case NgModel will inject DefaultValueAccessor. And now NgModel directive calls shared setUpControl function:
export function setUpControl(control: FormControl, dir: NgControl): void {
if (!control) _throwError(dir, 'Cannot find control with');
if (!dir.valueAccessor) _throwError(dir, 'No value accessor for form control with');
control.validator = Validators.compose([control.validator !, dir.validator]);
control.asyncValidator = Validators.composeAsync([control.asyncValidator !, dir.asyncValidator]);
dir.valueAccessor !.writeValue(control.value);
setUpViewChangePipeline(control, dir);
setUpModelChangePipeline(control, dir);
...
}
function setUpViewChangePipeline(control: FormControl, dir: NgControl): void
{
dir.valueAccessor !.registerOnChange((newValue: any) => {
control._pendingValue = newValue;
control._pendingDirty = true;
if (control.updateOn === 'change') updateControl(control, dir);
});
}
function setUpModelChangePipeline(control: FormControl, dir: NgControl): void {
control.registerOnChange((newValue: any, emitModelEvent: boolean) => {
// control -> view
dir.valueAccessor !.writeValue(newValue);
// control -> ngModel
if (emitModelEvent) dir.viewToModelUpdate(newValue);
});
}
And here is the bridge in action:
NgModel sets up control (1) and calls dir.valueAccessor !.registerOnChange method. ControlValueAccessor stores callback in onChange(2) property and fires this callback when input event happens (3). And finally updateControl function is called inside callback (4)
function updateControl(control: FormControl, dir: NgControl): void {
dir.viewToModelUpdate(control._pendingValue);
if (control._pendingDirty) control.markAsDirty();
control.setValue(control._pendingValue, {emitModelToViewChange: false});
}
where angular calls forms API control.setValue.
That's a short version of how it works.

Angular Providers

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.

Categories