I am attempting to migrate from Angular 8 to 11.
I have gotten everything to work except for a custom directive. The directive works when running the app using ng serve, but breaks when compiled with the --aot or --prod flags. The browser console log reports the following error:
core.js:10073 NG0303: Can't bind to 'hasPermission' since it isn't a known property of 'span'.
where hasPermission is the directive, and span is any element I attempt to use the directive. This used to work fine in Angular 8.
The directive itself:
import { Directive, OnInit, Input, ElementRef, TemplateRef, ViewContainerRef } from '#angular/core';
#Directive({
selector: '[hasPermission]',
})
export class PermissionsDirective implements OnInit {
constructor(
private element: ElementRef,
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef,
) {}
ngOnInit() {
...
}
// this is the structural call we're looking for
// the directive will respond to *hasPermission="'perm'"
#Input('')
set hasPermission(value: any) {
...
}
}
it is declared and exported using a SharedModule:
#NgModule({
declarations: [
...
PermissionsDirective,
...
],
exports: [
...
PermissionsDirective,
...
]
})
export class SharedModule {}
and finally, it's used in many other modules/components like so:
some.module.ts
import { SharedModule } from 'path/shared.module';
#NgModule({
imports: [
SharedModule,
],
declarations: [
SomeComponent
]
})
somehtml.html
...
<div *hasPermission="'somepermission'">...</div>
...
some.component.ts
#Component({ templateUrl: 'somehtml.html' })...
I'm thinking this has something to do with the -aot flag not placing modules in the right place so that they're visible? Otherwise I don't understand why there are no issues using just ng serve
Minimal example shown here: https://stackblitz.com/edit/angular-ivy-rzu5jm?file=src/app/app.component.html
Browser console for the stackblitz shows the same error (NG0303)
The issue is with #Input(''). You should not set anything inside the decorator, or if you do it needs to have the same name, that if you want to name it as an alias. Removing the empty string should fix it:
#Input()
set hasPermission(value: any) {
console.log(value);
}
FORKED STACKBLITZ
You must remove the rename from the decorator #Input('') -> #Input()
#Input()
set hasPermission(value: any) {
...
}
Related
I am developing an angular library. it has an internal service. which is defined like below.
Used providedIn to be tree-shakable. and didn't use providedIn:'root' because its internal and just used in the module scope.
#Injectable({
providedIn: MyChartModule,
})
export class LineChartLibService {
and when i wanted to add forRoot in module definition to have just one instance in lazy loading, it encounter Circular dependency.
export class MyChartModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: MyChartModule,
providers: [LineChartLibService],
};
}
}
what should we do in this situations?
is it possible to have a lazy load-able tree-shakable service in library?
WARNING: Circular dependency: dist\my-chart\esm2015\lib\my-chart.module.js -> dist\my-chart\esm2015\lib\line-chart\line-chart.component.js
-> dist\my-chart\esm2015\lib\line-chart-lib.service.js -> dist\my-chart\esm2015\lib\my-chart.module.js
I have previously answered a (non-duplicate) question that provides you with alternatives to this approach.
https://stackoverflow.com/a/60377431/5367916
Take this setup:
my-module.ts
declarations: [
MyComponent
]
my-service.ts
#Injectable({ providedIn: MyModule })
my-component.ts
constructor(private myService: MyService) {}
my-service imports my-module
my-module imports my-component
my-component imports my-service
There is a circular dependency.
A workaround
The workaround for this is to create a service module and import that into your module.
my-module.ts
imports: [
MyModuleServices
],
declarations: [
MyComponent
]
my-module-services.ts
// no imports or declarations
my-service.ts
#Injectable({ providedIn: MyModuleServices })
my-component.ts
constructor(private myService: MyService) {}
Alternative
The much more straightforward way is to add the service to your module's providers.
#NgModule({
providers: [ MyService ]
})
export class MyModule {}
I think what happens is the following:
You have the line-chart.component.js that probably calls for the LineChartLibService.
Within the LineChartLibService you say that this Injectable is provided in MyChartModule.
Within the MyChartModule you probably declare the line-chart.component.js to be part of this module.
This means the module asks for the component, which asks for the service, which asks for the module which then asks for the component again: A circular dependency.
If you add more code and context to your question, we might be able to make a better suggestion ;-)
I've got a PlayersModule and an ItemsModule.
I want to use the ItemsService in the PlayersService.
When I add it by injection:
import { Injectable } from '#nestjs/common';
import { InjectModel } from 'nestjs-typegoose';
import { ModelType, Ref } from 'typegoose';
import { Player } from './player.model';
import { Item } from '../items/item.model';
import { ItemsService } from '../items/items.service';
#Injectable()
export class PlayersService {
constructor(
#InjectModel(Player) private readonly playerModel: ModelType<Player>,
private readonly itemsService: ItemsService){}
I get this nest error :
[Nest] 11592 - 2018-8-13 11:42:17 [ExceptionHandler] Nest can't
resolve dependencies of the PlayersService (+, ?). Please make sure
that the argument at index [1] is available in the current context.
Both modules are imported in the app.module.ts. Both services are working alone in their module.
You have to export the ItemsService in the module that provides it:
#Module({
controllers: [ItemsController],
providers: [ItemsService],
exports: [ItemsService]
^^^^^^^^^^^^^^^^^^^^^^^
})
export class ItemsModule {}
and then import the exporting module in the module that uses the service:
#Module({
controllers: [PlayersController],
providers: [PlayersService],
imports: [ItemsModule]
^^^^^^^^^^^^^^^^^^^^^^
})
export class PlayersModule {}
⚠️ Don't add the same provider to multiple modules. Export the provider, import the module. ⚠️
Let' say you want to use AuthService from AuthModule in my TaskModule's controller
for that, you need to export authService from AuthModule
#Module({
imports: [
....
],
providers: [AuthService],
controllers: [AuthController],
exports:[AuthService]
})
export class AuthModule {}
then in TaskModule, you need to import AuthModule (note: import AuthModule not the AuthService in TaskModule)
#Module({
imports:[
AuthModule
],
controllers: [TasksController],
providers: [TasksService]
})
export class TasksModule {}
Now you should be able to use DI in TaskController
#Controller('tasks')
export class TasksController {
constructor(private authService: AuthService) {}
...
}
The question is answered by Kim Kern. But I just want to remind people who read through this comment. Whenever you get this error, you should follow these steps that may help you easily figure out where the stuck is:
Make sure the Module which provides providers was imported.
Make sure the provider which you are using is exported.
For example, you have category module which contains category service, post module has post service and it has category service as a dependency:
#Module({
controllers: [CategoryController],
providers: [CategoryService],
exports: [CategoryService] // Remember to export
})
export class CategoryModule {}
And
#Module({
imports: [CategoryModule], // Make sure you imported the module you are using
controllers: [PostController],
providers: [PostService]
})
export class PostModule {}
Don't forget to use this annotation.
Nest uses this to detect singleton class.
In spring boot - Java, this one used to be called Bean. Read more:
#Injectable()
export class PostService {
constructor(private readonly categoryService: CategoryService // This will be auto injected by Nestjs Injector) {}
}
I solved my problem by removing #Inject() from the argument in my constructor that was passing the exported service.
I believe that you faced the same problem i had. My scenario was 2 sibling custom modules (user, auth) that needed to use each other's services. I used circular DI to solve it. please check this link
Let me know whether if it solved your issue, maybe I can advise you further.
Solved my problem by changing the way of importing the constant string (TOKEN) used in #Inject()) of my provider... be careful using index.ts whith export * from module.ts, nest won't resolve the dependecy
Based on the answer by Kim Kern nowadays we should add only injected service into our service without any decorators (#Inject() doesn't required). After that it will work right. That was my mistake and probably can help others.
Steps 1. Export the file that you want
Step 2. Import the whole module.
I initially made a mistake of adding the file as provider and also adding the module which was throwing error.
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.
So I am currently trying to use some jquery plugins like Magnific-Popup etc in my Angular 4 application but I'm having some trouble. I installed jquery by doing npm install --save-dev #types/jquery in my terminal and that worked fine, but I'm having trouble getting the actual plugins to work. This is what I tried initially,
#Component({
selector: 'app-showcase',
templateUrl: './showcase.component.html',
styleUrls: ['./showcase.component.css']
})
export class ShowcaseComponent implements OnInit {
#ViewChild("elementRef") elref2: ElementRef;
constructor(private elRef: ElementRef) { }
ngOnInit() {
jQuery(this.elref2.nativeElement).magnificPopup();
}
ngAfterViewInit() {
}
In order to get typescript to recognize the magnificPopup() function, I tried importing the scripts from the magnificPopup documentation into my index.html (at the bottom of the body tag), but as far as I can tell that is not working. Webstorm says "can not resolve file jquery.magnific-popup.js". I'm also loading the jquery.min script as well, and that seems to work fine.
I also tried running npm-install magnific-popup, but that also failed. Additionally, I tried adding the script into the angular.JSON file but to no avail.
I guess what I'm asking is, what is and how can I go about installing and using a third-party jquery plugin like magnific-pop up into my angular 4 application?
Thanks, any help is appreciated.
I have an Angular project where I need to use (for now) a jquery dependent plugin (nanoscroller). I use angular-cli to do all my build processes and my angular-cli.json file has the following under the "scripts" property:
"scripts": [
"../node_modules/jquery/dist/jquery.js",
"../node_modules/nanoscroller/bin/javascripts/jquery.nanoscroller.js"
]
Then I use the plugin as follows:
import { AfterViewInit, Component, ElementRef, OnDestroy, Renderer, ViewChild } from "#angular/core";
declare var jQuery: any;
#Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.scss"]
})
export class AppComponent implements AfterViewInit {
layoutMenuScroller: HTMLDivElement;
#ViewChild("layoutMenuScroller") layoutMenuScrollerViewChild: ElementRef;
constructor() {
}
ngAfterViewInit() {
this.layoutMenuScroller = <HTMLDivElement>this.layoutMenuScrollerViewChild.nativeElement;
jQuery(this.layoutMenuScroller).nanoScroller({ flash: true });
}
// etc..
}
Perhaps you can adopt this to your use-case.
I have an app with 2 modules so far: "Shared" and "Web".
All my components in web are working fine. I just created a simple component in the Shared module since I plan to re-use it throughout the app:
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'pb-ds-pluginstable',
templateUrl: './pluginstable.component.html',
styleUrls: ['./pluginstable.component.scss']
})
export class PluginstableComponent implements OnInit {
#Input() name: string;
#Input() version: string;
#Input() link: string;
constructor() {}
ngOnInit() {}
}
This is imported into the shared module, and exported:
...other imports...
import { PluginstableComponent } from './pluginstable/pluginstable.component';
#NgModule({
imports: [
CommonModule,
RouterModule,
NgbModule
],
declarations: [HeaderComponent, FooterComponent, HeaderShadowDirective, PluginstableComponent],
exports: [HeaderComponent, FooterComponent, HeaderShadowDirective, PluginstableComponent]
})
export class SharedModule { }
But when I use this in a component's template in the Web module, like this:
<pb-ds-pluginstable name="foo" version="2.2.2" link="bar"></pb-ds-pluginstable>
I get an error that the component is not recognized:
core.es5.js:1020 ERROR Error: Uncaught (in promise): Error: Template parse errors:
'pb-ds-pluginstable' is not a known element:
1. If 'pb-ds-pluginstable' is an Angular component, then verify that it is part of this module.
2. If 'pb-ds-pluginstable' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '#NgModule.schemas' of this component to suppress this message. ("
</section>
[ERROR ->]<pb-ds-pluginstable name="foo" version="2.2.2" link="bar"></pb-ds-pluginstable>
What am I missing here?
You need also to import the SharedModule in that(not parent, not child, exact that module) module, where you want to use it's exported elements
#NgModule({
imports: [
SharedModule,
...
]
})
export class YourModule { }
I asume that you are not importing the shared module into the web module. Most likely you added the import only into the app module
in shared module
you have to export that component.
suppose you have moduleone module which has component componentone you want to add moduleone in shared module moduleshared, which will be added into moduletwo.
I have to export component in moduleshared
that was the problem.
I added my module moduleone in moduleshared but forgot to import, once I added then its available in other component of moduletwo