Angular 4: When and why is #Inject is used in constructor? - javascript

Problem Statment
I am learning Angular 4 and I have stumble upon a code where #Inject is being used in a constructor and I am not able to figure out why...
Code and Source
I am using Angular 4 Material
Code Source: https://material.angular.io/components/dialog/overview
In the code, they are injecting MAT_DIALOG_DATA
constructor(public dialogRef: MatDialogRef<DialogOverviewExampleDialog>,
#Inject(MAT_DIALOG_DATA) public data: any
) { }
Can anyone please elaborate what does it mean and when/where should we do this?

#Inject() is a manual mechanism for letting Angular know that a
parameter must be injected.
import { Component, Inject } from '#angular/core';
import { ChatWidget } from '../components/chat-widget';
#Component({
selector: 'app-root',
template: `Encryption: {{ encryption }}`
})
export class AppComponent {
encryption = this.chatWidget.chatSocket.encryption;
constructor(#Inject(ChatWidget) private chatWidget) { }
}
In the above we've asked for chatWidget to be the singleton Angular
associates with the class symbol ChatWidget by calling
#Inject(ChatWidget). It's important to note that we're using
ChatWidget for its typings and as a reference to its singleton.
We are not using ChatWidget to instantiate anything, Angular does
that for us behind the scenes
From https://angular-2-training-book.rangle.io/handout/di/angular2/inject_and_injectable.html

If MAT_DIALOG_DATA is a non-factory/class dependency (like string for your configs), you usually use #Inject.
Also check InjectionToken: https://angular.io/guide/dependency-injection#injectiontoken
One solution to choosing a provider token for non-class dependencies is to define and use an InjectionToken
Here's a plunker: http://plnkr.co/edit/GAsVdGfeRpASiBEy66Pu?p=preview
if you remove #Inject in these cases you will receive a
Can't resolve all parameters for ComponentName: (?)

IoC container in Angular uses the type declarations in the constructor to determine the objects to be injected to the constructor parameters.
In your example, "public data: any" parameter could not be determined by its type declaration because it's defined as "any". In order to solve this problem, you have to use "#Inject(MAT_DIALOG_DATA)" decorator to inform the IoC container about the object that must be injected to "data" parameter.
Also in your example, "#Inject" decorator is used with an InjectionToken to complicate things a little more :)
An InjectionToken is actually a class which is used to name the objects to be used by IoC container to inject in to other classes. Normally you could use any classes name as a token for IoC injection (like "MatDialogRef<DialogOverviewExampleDialog>" in your example) and this works fine. But when you start writing your UnitTests you realize that you need to use Mock objects instead of real objects to be injected into your classes and when you use real class names as your tokens, you could not do that.
To solve this problem you could use Interfaces as token names and this is actually the right solution, but since JavaScript does not support interfaces you could not use Interface names as tokens, because transpiled code does not contain Interface definitions.
As a result of all this, you need to use InjectionToken. An InjectionToken allows you to inject any object into your constructor. You just need to declare it in your modules and map to the real class that you want to be injected. This way you could use different classes for your production and test codes.

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

How to design a global class for storing settings in Angular?

I have added a class like this.
export class Settings{
public data: string = "blopp";
}
When I try to access the data, it seems that the field I'm trying to assign that value to sees the class Settings itself but it doesn't recognize the data thingy.
How do I redesign the class to provide settings for other components?
I've read about #Output decorator but since I won't be binding to the values, it seems not the correct approach. I've made sure that the class is imported and recognized withing the component that's supposed to consume it. I've also tried the corresponding exposure but using a function in the class with settings - the same, failed result.
If you're using angular-cli and going to store in this class environment specific settings - you already have built in support for this.
Put the setting into environment.ts. For example:
export const environment = {
production: false,
someSetting: 'foo',
};
Then it can be consumed from anywhere within the app:
import { environment } from "../environments/environment"; //fix according to your project structure
#Injectable()
export class SampleService {
private foo = environment.someSetting;
}
Here you can find more info on how to add more environments and build you project with specific environment settings.
Your best bet for storing global settings is to use a service. The code for that looks like this:
import { Injectable } from '#angular/core';
#Injectable()
export class DataService {
serviceData: string;
}
I have a blog post about this here: https://blogs.msmvps.com/deborahk/build-a-simple-angular-service-to-share-data/
And a plunker here: https://plnkr.co/edit/KT4JLmpcwGBM2xdZQeI9?p=preview
Also, the #Output decorator is only for communication between a child component and a parent component where the child is nested within the parent.

Angular 2 Shared Data Service is not working

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.

Service inheritance with Angular 1.x and ES6

I am using Angular 1.6 with ES6. Until now I implemented inheritence with the extend keyword on ES6 classes
export default class myChildService extends myBaseService {
constructor(someDependency){
'ngInject'
super();
someDependency.doStuff();
}
}
and registered the child-class as angular-service
import myChildService from ./myChildService
angular.module('myApp', [])
.service('myChildService', myChildService)
This works well so far!
However I would like to split my application into smaller modules. This means I will have several different angular-modules which will need access to myBaseService in order to inherit the functionality.
I could just import the file containing myBaseService, but this seems not very angulary. It basically means I disregard angular-modules and DI everytime I use inheritance.
Question:
Is there any way to export an ES6-class from an angular-module base so that I can reuse it on an angular-module child which depends on base?
Maybe I am just looking at this the wrong way - if you have other suggestions to implement inheritance of angular-services/ factories using ES6-classes, please go ahead.
Why importing is not angulary? If you would like to use base service functionality, the module should know it's definition.

How does service constructor get instaniated in angular 2?

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.

Categories