Is it possible to somehow dependency inject components in Angular? I would like to be able to do something similar to what you can do with services e.g.:
my.module.ts:
providers: [
{
provide: MyService,
useClass: CustomService
}
]
I have tried to use *ngIf="condition" in a wrapper component, but it will then complain about services not being provided for the components I do not wish to use.
It is fully possible if you have parent-child relationship between the component and injecting component.
so if you have the structure like this
#Component( {
selector:"app-parent",
template:" <app-child> </app-child>"
} )
export class ParentComp { ...}
you could inject parent-component inside the child component via dependency injection
#Component({
selector:"app-child",
template:"I am child"
})
export class ChildComponent{
constructor(private parentComp:ParentComponent){
}
}
Angular DI will now that you are asking for parent component that child component lives in and will inject it for you.
If you want to inject component not parent-child relationship like, so for example you want to inject the sidenav component into the some table component that lives outside the sidenav, it is hardly achiavable (not recommended also), but possible. if you want to do that, you should probably create shared service, that will share the states between these components.
Sure, you can provide any value (const, function, class) for the particular injection token. You can find some examples with components providing when we are going to make ControlValueAccessor
#Component({
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}
]
})
export class CustomInputComponent {...}
You can create your own injection token and provide any stuff you want and components also.
/* In tokens.ts */
const MY_TOKEN_NAME = new InjectionToken<MyAmazingComponent>('MY_TOKEN_NAME')
/* In module */
providers: [
{ provide: MY_TOKEN_NAME, useClass: MyAmazingComponent }
]
I created a library Angular-Slickgrid which is a wrapper of a jQuery data grid library and is Open Source. It all work nicely when there's only 1 grid (component) on the page but when I want to create 2 of these components (same selector) on the same page, I start to get lot of weird behaviors. The behavior I can see is that some of 1st functions affects the 2nd grid. I can deal with the Services singleton, but in my case it's really the properties of the component that get override by the last created component, why is that? I thought each Angular components were totally independent (apart from the Services), so what am I doing wrong?
I use ng-packagr to create my lib and the ngModule of the component is the following
#NgModule({
imports: [
CommonModule,
TranslateModule
],
declarations: [
AngularSlickgridComponent,
SlickPaginationComponent
],
exports: [
AngularSlickgridComponent,
SlickPaginationComponent
],
entryComponents: [AngularSlickgridComponent]
})
export class AngularSlickgridModule {
static forRoot(config: GridOption = {}) {
return {
ngModule: AngularSlickgridModule,
providers: [
{provide: 'config', useValue: config},
CollectionService,
ControlAndPluginService,
ExportService,
FilterService,
GraphqlService,
GridEventService,
GridExtraService,
GridOdataService,
GridStateService,
GroupingAndColspanService,
OdataService,
ResizerService,
SharedService,
SortService
]
};
}
}
The component class starts with
#Injectable()
#Component({
selector: 'angular-slickgrid',
templateUrl: './angular-slickgrid.component.html',
providers: [ResizerService]
})
export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnInit {
Then in my App, I call the external grid module like this
imports: [
AppRoutingRoutingModule,
BrowserModule,
HttpClientModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: (createTranslateLoader),
deps: [HttpClient]
}
}),
AngularSlickgridModule.forRoot({
// add any Global Grid Options/Config you might wantApp
enableAutoResize: true
})
],
Then I can create 2 grids in my View like this
<angular-slickgrid gridId="grid1"
[columnDefinitions]="columnDefinitions"
[gridOptions]="gridOptions"
gridHeight="200"
gridWidth="800"
[dataset]="dataset">
</angular-slickgrid>
<hr>
<angular-slickgrid gridId="grid2"
[columnDefinitions]="columnDefinitions2"
[gridOptions]="gridOptions2"
gridHeight="200"
gridWidth="800"
[dataset]="dataset2">
</angular-slickgrid>
After spending a lot of time debugging, I did find out that the 1st component completely override the properties of the 2nd component. If I destroy the 2nd component, it doesn't fix the issue. For example, I click on a column to sort it on both grid, when I click on "clearSort()" from the 1st grid, it actually clears the sort of the 2nd grid!? I also found that properties of only the last created grid remains, if I click on "clearSort()" from 1st or 2nd grid, it will clear it in the 2nd always.
I know how to deal with Services Singleton, but my issue is really the properties of the class that are somehow shared by the 2 components... or to put it in another perspective, 1st component class properties get overridden by 2nd component properties
Is there something that I'm missing to make these 2 components completely independent? I have been searching and trying for couple of hours already, is that even possible or is that normal behavior?
EDIT
If you want to see all the code, everything is available from GitHub, you can see the 2 grids code (which is currently on a separate branch):
View
Component
App Module
Library Component
Library Module
EDIT 2
After all these hours, I found out that it was related to Services Singleton. Answered my own question down below. Hopefully this will help someone else facing similar issues.
See below for the behavior, watch the data but also the blue sort icons, it all happens on the 2nd grid while I do the action on 1st grid
Both component instances, even of the same component class, should have their own scope. Their variables are encapsulated and unique if they aren't declared as static.
Are you sure that dataset and dataset2 do not share the same reference? Avoid following, even for tests:
private dataset = [data1, data2];
private dataset2 = dataset;
That would enforce the described weired behaviour if you input dataset and dataset2 to two different components.
You are wrapping a jquery plugin which itself is plain javascript. Maybe the wrapped javascript is revoking angulars component scoping?
Are you sure that component instances do not share data by services mistakenly?
Wow I found the issue and I did not expect what I found to be the issue... My library had no providers in it, and so all Services were acting as Singleton. Because of that, any Services function call were using the internal variables (grid, gridOptions, dataView) of the last created grid. So the only thing that I had to do, in order to fix this, was to provide all Services into the providers array.
BEFORE
#Injectable()
#Component({
selector: 'angular-slickgrid',
templateUrl: './angular-slickgrid.component.html',
providers: [ResizerService]
})
export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnInit {
AFTER
#Injectable()
#Component({
selector: 'angular-slickgrid',
templateUrl: './angular-slickgrid.component.html',
providers: [
ControlAndPluginService,
ExportService,
FilterService,
GraphqlService,
GridEventService,
GridExtraService,
GridStateService,
GroupingAndColspanService,
ResizerService,
SortService
]
})
export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnInit {
Oh my... so many hours wasted for such simple thing
I am using Angular 5+ and I want to create 3 levels up nested components.
Here is an example of what I can do.
<my-app>
<first></first>
<second></second>
</myapp>
And here is what I cannot do.
<my-app>
<first><second></second></first>
</myapp>
I have the following code in my app module.
#NgModule({
declarations: [
AboutPage,FirstComponent,SecondComponent
],
imports: [
IonicPageModule.forChild(AboutPage),
],
})
export class AppModule{}
Note here that AppModule is not the root module but it is lazyLoaded Component as well.
you will have to implement the <second></second> component inside of the <first></first>'s components template.
#Component({
selector: 'first',
template: '<second></second>'
})
export class FirstComponent { ... }
your module is correct
MyAppComponent needs to have an <ng-content> element, otherwise it won't display projected content.
Caution: This only works for components that are not the root component. Angular doesn't support projecting content to the root component. See comment below the question to see what causes the confusion.
1) Created a new directive with angularCLI.
import {Directive, ElementRef, OnInit} from '#angular/core';
#Directive({
selector: '[scrollable]'
})
export class ScrollableDirective implements OnInit{
constructor(public el:ElementRef) { }
ngOnInit(){
console.log('its working!')
}
}
2) Angular CLI automatically adds the directive to the app.module declarations
import { ScrollableDirective } from './scrollable/scrollable.directive';
#NgModule({
declarations: [
...
ScrollableDirective
],
3) Try to use the directive as an attribute
<div class="menu-container" *ngIf="menuService.showMenu" [scrollable]>
4) Resulting error
Error: Uncaught (in promise): Error: Template parse errors:
Can't bind to 'scrollable' since it isn't a known property of 'div'.
I have read the official documentation and I seem to be doing all the right things. I cannot understand what I could have missed and why the directive cannot be used.
Try adding the scrollable directive without the [] bindings:
<div class="menu-container" *ngIf="menuService.showMenu" scrollable>
[] would be if you are passing a value to the directive, but you aren't utilizing any #Input values in you directive, so it would not be needed.
The docs use the binding brackets [highlightColor]="'orange'" because it's expecting a string value from the consumer to specify a color. #Input would only be needed if you are needing a value passed to the attribute directive to use in some way.
#Kevin is right that the error is being caused by #Input not being added to the directive configuration, but in this case you don't need it, so avoid the import/export of that decorator.
What I'm trying to do is create a service that uses a model to show an alert. The alert-model should be necessary nowhere else but in that service but I am not able to make this work. My service:
import {Injectable, Inject} from "angular2/core";
import {AlertModel} from "../models/alert.model";
#Injectable()
export class AlertService {
constructor(#Inject(AlertModel) alertModel: AlertModel) {
}
public alert(){
this.alertModel.message = 'success';
//...
}
}
But I keep getting this error:
Uncaught (in promise): No provider for AlertModel! (UserComponent -> AlertService -> AlertModel)
I'm new to angular and I do not understand this. What am I missing? Thanks in advance!
You need to provide the AlertModel somewhere
bootstrap(AppComponent, [AlertModel])
or in the root component (preferred):
#Component({
selector: 'my-app',
providers: [AlertModel],
...
})
Ensure AlertModel has the #Injectable() decorator and all its constructor parameters are provided as well (if it has any)
#Inject(AlertModel) is redundant if the type of the constructor parameter is already AlertModel. #Inject() is only necessary if the type differs or if AlertModel doesn't have the #Injectable() decorator.
constructor(#Inject(AlertModel) alertModel: AlertModel) {
You have this error since there is no provider for the AlertModel class visible from the UserComponent component (that calls the service). You can define either this class in the providers attribute of the component either when bootstrapping your application.
See the question to know more about how hierarchical injectors works and how to inject things into services:
What's the best way to inject one service into another in angular 2 (Beta)?
Since the AlertModel class seems to be a model class I don't think that you need to inject it. You can simply import the class and instantiate it:
#Injectable()
export class AlertService {
alertModel: AlertModel = new AlertModel();
public alert(){
this.alertModel.message = 'success';
//...
}
}