Angular 2 Karma Test 'component-name' is not a known element - javascript

In the AppComponent, I'm using the nav component in the HTML code. The UI looks fine. No errors when doing ng serve. and no errors in console when I look at the app.
But when I ran Karma for my project, there is an error:
Failed: Template parse errors:
'app-nav' is not a known element:
1. If 'app-nav' is an Angular component, then verify that it is part of this module.
2. If 'app-nav' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '#NgModule.schemas' of this component to suppress this message.
In my app.module.ts:
there is:
import { NavComponent } from './nav/nav.component';
It is also in the declarations part of NgModule
#NgModule({
declarations: [
AppComponent,
CafeComponent,
ModalComponent,
NavComponent,
NewsFeedComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
JsonpModule,
ModalModule.forRoot(),
ModalModule,
NgbModule.forRoot(),
BootstrapModalModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
I'm using the NavComponent in my AppComponent
app.component.ts
import { Component, ViewContainerRef } from '#angular/core';
import { Overlay } from 'angular2-modal';
import { Modal } from 'angular2-modal/plugins/bootstrap';
import { NavComponent } from './nav/nav.component';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Angela';
}
app.component.html
<app-nav></app-nav>
<div class="container-fluid">
</div>
I have seen a similar question, but the answer in that question says we should add NgModule in the nav component that has a export in that, but I'm getting compile error when I do that.
There is also: app.component.spec.ts
import {NavComponent} from './nav/nav.component';
import { TestBed, async } from '#angular/core/testing';
import { AppComponent } from './app.component';

Because in unit tests you want to test the component mostly isolated from other parts of your application, Angular won't add your module's dependencies like components, services, etc. by default. So you need to do that manually in your tests. Basically, you have two options here:
A) Declare the original NavComponent in the test
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent,
NavComponent
]
}).compileComponents();
}));
B) Mock the NavComponent
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent,
MockNavComponent
]
}).compileComponents();
}));
// it(...) test cases
});
#Component({
selector: 'app-nav',
template: ''
})
class MockNavComponent {
}
You'll find more information in the official documentation.

You can also use NO_ERRORS_SCHEMA
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
https://2018.ng-conf.org/mocking-dependencies-angular/

For me importing the component in the parent resolved the issue.
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent,
NavComponent
]
}).compileComponents();
}));
Add this in spec of the parent where this component is used.

One more reason is that there can be multiple .compileComponents() for beforeEach() in your test case
for e.g.
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TestComponent]
}).compileComponents();
}));
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientModule],
declarations: [Test1Component],
providers: [HttpErrorHandlerService]
}).compileComponents();
});

Step 1: Create stubs at beginning of spec file.
#Component({selector: 'app-nav', template: ''})
class NavComponent{}
Step 2: Add stubs in component's declarations.
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent,
NavComponent
],
}).compileComponents();

Another source for this error are the tests for the parent component, app-root, that include the tag of this component, app-nav.
Even though the message is about app-nav, the child component, the fix should be added in the tests of app-root, the parent component.
The fix can be a mock:
app.component.spec.ts:
#Component({selector: 'app-nav', template: ''})
class NavComponentStub {
}
What happens is that you create the root component, with it's tests, they pass. Latter on you add the child component in the root component tag and now you have to update the root component tests even if you just added a tag in the template.
Also the message doesn't say which test fails and from the message you might be led to believe that it's the child component's tests, when in fact they are the parent's tests.

If you create a stub and still get the same error it might be because of --watch mode on. Try to stop it and run again.

Related

Error while using “ElementRef” in exported component

I'm using ElementRef in the CommentComponent which is exported to others modules like ArticleModule, ProductModule etc...
// CommentModule
#NgModule({
exports: [ CommentComponent ],
declarations: [ CommentComponent ],
providers: [ CommentService]
})
export class CommentModule { }
// Comment Component (belongs to CommentModule)
#Component({
selector: 'comments',
templateUrl: 'comment.component.html',
styleUrls: ['comment.component.scss']
})
export class CommentComponent implements OnInit {
suggOnTyping: string = 'hidden';
constructor(private _eref: ElementRef){}
#HostListener('document:click', ['$event'])
onClickOutside(event: any) {
event.preventDefault();
event.stopPropagation();
if (!this._eref.nativeElement.contains(event.target))
this.suggOnTyping = 'hidden';
}
}
// Article Module
#NgModule({
imports: [
CommonModule,
RouterModule,
CommentModule,
ArticleRoutingModule],
declarations: [ ArticleComponent ],
providers: [ArticleService, CommentService, CommentComponent],
schemas: [ NO_ERRORS_SCHEMA ]
})
ArticleComponent calls the CommentComponent from view like this:
<div class="article">
.......
<comments></comments>
</div>
Now when I'm trying to route through ArticleComponent, I'm getting:
core.js:1673 ERROR Error: Uncaught (in promise): Error: StaticInjectorError(AppModule)[NgClass -> ElementRef]:
StaticInjectorError(Platform: core)[NgClass -> ElementRef]:
NullInjectorError: No provider for ElementRef!
Error: StaticInjectorError(AppModule)[NgClass -> ElementRef]:
StaticInjectorError(Platform: core)[NgClass -> ElementRef]:
NullInjectorError: No provider for ElementRef!
It seems ElementRef cannot pass through 'provider' because when I remove it from CommentComponent everything works fine.
What is the problem ?
I'm using Angular 6 by the way
Remove CommentComponent from providers list of AritcleModule. CommentComponent is already declared in CommentModule.
// Article Module
#NgModule({
declarations: [ ArticleComponent],
providers: [ArticleService, CommentService, CommentComponent ], //<-- remove from here
schemas: [ NO_ERRORS_SCHEMA ]
})
Remove ElementRef from constructor which is throwing this error and if you want to access the element then you can put the reference for element and use #ViewChild decorator in ts file.
Example :
html
<div #ele>hello/div> <!-- element reference goes here -->
ts
#ViewChild("ele") _eref: ElementRef;

Error when using AngularJS component inside Angular app: "Error: Trying to get the AngularJS injector before it being set."

I'm working through Angular's upgrade guide to learn how to embed AngularJS components in an Angular app. I've created a bare-bones Angular app using the Angular CLI and added a simple AngularJS module as a dependency.
When I run ng serve, the application compiles with no errors. However, at runtime, I get this message in the console:
Error: Trying to get the AngularJS injector before it being set.
What is causing this error, and how can I avoid it? I haven't deviated from the steps detailed in the upgrade guide.
Here's how I'm upgrading my AngularJS component inside my Angular app:
// example.directive.ts
import { Directive, ElementRef, Injector } from '#angular/core';
import { UpgradeComponent } from '#angular/upgrade/static';
// this is the npm module that contains the AngularJS component
import { MyComponent } from '#my-company/module-test';
#Directive({
selector: 'my-upgraded-component'
})
export class ExampleDirective extends UpgradeComponent {
constructor(elementRef: ElementRef, injector: Injector) {
// the .injectionName property is the component's selector
// string; "my-component" in this case.
super(MyComponent.injectionName, elementRef, injector);
}
}
And here's my app.module.ts:
// app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { UpgradeModule } from '#angular/upgrade/static';
import { ExampleDirective } from './example.directive';
import { myModuleName } from '#my-company/module-test';
#NgModule({
declarations: [AppComponent, ExampleDirective],
imports: [BrowserModule, AppRoutingModule, UpgradeModule],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
constructor(private upgrade: UpgradeModule) {}
ngDoBootstrap() {
this.upgrade.bootstrap(document.body, [myModuleName], {
strictDi: true
});
}
}
I'm using Angular 5.2.0.
I faced the same issue, and finally solved it. There are some steps to follow before bootstrap an hybrid Angular/angularjs application.
Install the UpgradeModule npm install #angular/upgrade
Wrap your "CompanyModule" (the module where all your company components are registered) into a new angularjs module (for instance: Ng1Shared). If you not have a module for your company components, it must be created. Than downgrade AppComponent as shown below.
const MyCompanyModule = angular
.module('MyCompanyModule', [])
.component('myComponent', MyComponent)
.name;
const Ng1Shared = angular
.module('Ng1Shared', [MyCompanyModule])
.directive('appRoot', downgradeComponent({ component: AppComponent }))
.name;
Configure AppModule with basic imports (BrowserModule, CommonModule, UpgradeModule). Provide the angularjs' Injector to Angular; declare an "entryComponent" and remove the default bootstrap for AppComponent.
#NgModule({
imports: [BrowserModule, CommonModule, UpgradeModule],
declarations: [AppComponent],
providers: [{provide: '$scope', useExisting: '$rootScope'}], // REQUIRED
entryComponents: [AppComponent], // ADD AN ENTRY COMPONENT
// bootstrap: [AppComponent] MUST BE REMOVED
})
Set angularjs globally with a function provided by UpgradeModule itself and manually bootstrap Angular with DoBootstrap method provided by #angular/core.
export class AppModule implements DoBootstrap {
constructor(private upgrade: UpgradeModule) { }
public ngDoBootstrap(app: any): void {
setAngularJSGlobal(angular);
this.upgrade.bootstrap(document.body, [Ng1Shared], { strictDi: false });
app.bootstrap(AppComponent);
}
}
Create a wrapper directive for every angularjs component and add it to AppModule's declaration array.
#Directive({
selector: 'my-component'
})
export class MyComponentWrapper extends UpgradeComponent {
#Input() title: string;
constructor(elementRef: ElementRef, injector: Injector) {
super('myComponent', elementRef, injector);
}
}
I wrote a simple example available on stackblitz.
For example purposes I added angularjs MyCompanyModule to another angularjs module, called Ng1Module. As you can see also property binding between angularjs and angular component works fine.
I hope it can be useful.
https://github.com/angular/angular/issues/23141#issuecomment-379493753
you cannot directly bootstrap an Angular component that contains
upgraded components before bootstrapping AngularJS. Instead, you can
downgrade AppComponent and let it be bootstrapped as part of the
AngularJS part of the app:
https://stackblitz.com/edit/angular-djb5bu?file=app%2Fapp.module.ts
try to add an entryComponents to your AppModule like this :
...
#NgModule({
declarations: [AppComponent, ExampleDirective],
imports: [BrowserModule, AppRoutingModule, UpgradeModule],
entryComponents: [
AppComponent // Don't forget this!!!
],
providers: [],
// bootstrap: [AppComponent] // Delete or comment this line
})
...

Angular passing dynamic entry components into module and then passing them again into another module

I'm making a modal component for a component library. I made a 3rd party modal library that I'm using within my component library. A main feature is being able to pass a component via a service and dynamically adding it to the modal.
My modal lib has a static method that allows you to add your component to the module's entry components. It looks like:
export class A11yModalModule {
static withComponents(components: any[]) {
return {
ngModule: A11yModalModule,
providers: [{
provide: ANALYZE_FOR_ENTRY_COMPONENTS,
useValue: components,
multi: true
}]
};
}
}
Cool, that works. I can pass components into it when I import the module like this: A11yModalModule.withComponents([ModalContentComponent])
My problem occurs when I abstract this out another level. So now instead of 2 modules I have 3. I need to pass a component like I did above from the lib consumer's module, to my component module, and then into the modal module.
How can I pass components from the lib module to the modal module?
I think I'm getting close. Here are my 3 modules
// app module
#NgModule({
declarations: [AppComponent, ModalContentComponent],
imports: [
LibModalModule.withComponents([ModalContentComponent])
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
// lib module
#NgModule({
imports: [CommonModule],
declarations: [LibModal],
providers: [LibModalService],
exports: []
})
export class LibModalModule {
static withComponents(components: any[]) {
return {
ngModule: LibModalModule,
imports: [CommonModule, A11yModalModule.withComponents(components)]
};
}
}
// a11y modal module
#NgModule({
imports: [CommonModule],
declarations: [ModalComponent],
exports: [],
providers: [ModalService, DomService],
entryComponents: [ModalComponent]
})
export class A11yModalModule {
static withComponents(components: any[]) {
return {
ngModule: A11yModalModule,
providers: [{
provide: ANALYZE_FOR_ENTRY_COMPONENTS,
useValue: components,
multi: true
}]
};
}
}
withComponents method should return ModuleWithProviders object which is just wrapper around a module that also includes the providers.
It can't have imports property or something else because it doesn't understand those properties. Here's an excerpt from angular source code that is responsible from reading metadata from ModuleWithProviders:
else if (importedType && importedType.ngModule) {
const moduleWithProviders: ModuleWithProviders = importedType;
importedModuleType = moduleWithProviders.ngModule;
if (moduleWithProviders.providers) {
providers.push(...this._getProvidersMetadata(
moduleWithProviders.providers, entryComponents,
`provider for the NgModule '${stringifyType(importedModuleType)}'`, [],
importedType));
}
}
As we can see angular compiler takes providers from the object that will returned in withComponents method.
So, in order to merge your modules you can either use your approach(provide ANALYZE_FOR_ENTRY_COMPONENTS in LibModalModule.withProviders) or reuse A11yModalModule.withComponents like:
#NgModule({
imports: [CommonModule, A11yModalModule],
providers: [LibModalService],
exports: []
})
export class LibModalModule {
static withComponents(components: any[]) {
return {
ngModule: LibModalModule,
providers: A11yModalModule.withComponents(components).providers
};
}
}
(Tested with AOT)
Also A11yModalModule has to be imported in LibModalModule if we want its providers to be included in our root module injector (And i suppose you're going to use ModalService and DomService that are declated in A11yModalModule). The reason of this is that angular includes all providers from transitive module in root module injector.
See also:
Avoiding common confusions with modules in Angular
What you always wanted to know about Angular Dependency Injection tree
I had a bug that was giving me a false flag. Turns out you can just add the same withComponents method to the component library module and it passes the component through. I'd love an explanation on how this works if anyone knows.
// app module
#NgModule({
declarations: [AppComponent, ModalContentComponent],
imports: [
LibModalModule.withComponents([ModalContentComponent])
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
// lib module
#NgModule({
imports: [CommonModule, A11yModalModule],
declarations: [LibModal],
providers: [LibModalService],
exports: []
})
export class LibModalModule {
static withComponents(components: any[]) {
return {
ngModule: LibModalModule,
providers: [{
provide: ANALYZE_FOR_ENTRY_COMPONENTS,
useValue: components,
multi: true
}]
};
}
}
// a11y modal module
#NgModule({
imports: [CommonModule],
declarations: [ModalComponent],
exports: [],
providers: [ModalService, DomService],
entryComponents: [ModalComponent]
})
export class A11yModalModule {
static withComponents(components: any[]) {
return {
ngModule: A11yModalModule,
providers: [{
provide: ANALYZE_FOR_ENTRY_COMPONENTS,
useValue: components,
multi: true
}]
};
}
}

Angular: Passing a function to dumb component

I'm writing an AWS security groups module clone with Angular and ngrx. I'm trying to pass a method into the dumb component for creating a new security group.
...
const schemas: any[] = [];
#NgModule({
imports: [
CommonModule
],
declarations: [
SecurityGroupsComponent,
SecurityGroupsTableComponent,
SecurityGroupsListComponent
],
schemas: schemas,
exports: [SecurityGroupsComponent]
})
export class SecurityGroupsModule { }
import { Component } from '#angular/core';
#Component({
selector: 'app-security-groups-table',
templateUrl: './security-groups-table.component.html',
})
export class SecurityGroupsTableComponent {
onCreateSecurityGroup() {
console.log('Create Security Group');
}
}
<app-security-groups-list
[createSecurityGroup]="onCreateSecurityGroup"></app-security-groups-list>
import { Component, Input } from '#angular/core';
#Component({
selector: 'app-security-groups-list',
templateUrl: './security-groups-list.component.html',
})
export class SecurityGroupsListComponent {
#Input()
createSecurityGroup: Function;
selectSecurityGroup(securityGroupId: string) {
this.securityGroupSelected.next(securityGroupId);
}
}
and when the template is rendered I get this exception:
Can't bind to 'createSecurityGroup' since it isn't a known property of 'app-security-groups-list'.
What am I doing wrong?
It seems you have missed to add SecurityGroupsListComponent to NgModule.
Please make sure you do below step before use it:
#NgModule({
imports: [...],
declarations: [SecurityGroupsListComponent]
})
export const DECLARATIONS = [SecurityGroupsComponent,
SecurityGroupsTableComponent,
SecurityGroupsListComponent]
and then
declarations: DECLARATIONS,
schemas: schemas,
exports: DECLARATIONS

Ionic 2: unit test for component with ionic's markup/elements

I have a simple Angular 2 component for an Ionic 2 app. The component uses some Ionic's markup, such as:
<ion-card>
<h3>{{ rawcontent.name }}</h3>
<p *ngIf="rawcontent.description">{{ rawcontent.description }}</p>
</ion-card>
the component .ts is something like:
import { Component, Input } from '#angular/core';
import { NavController } from 'ionic-angular/';
import { Content } from '../../pages/content/content';
#Component({
selector: 'content-detail',
templateUrl: 'content-detail.html'
})
export class ContentDetailComponent {
#Input('data') rawcontent: any = {};
constructor(public nav: NavController) {
}
//other methods
}
I'm trying to write an unit test for it, but I got this error so far:
'ion-card' is not a known element:
1. If 'ion-card' is an Angular component, then verify that it is part of this module.
2. If 'ion-card' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '#NgModule.schemas' of this component
to suppress this message.
I don't know what to do now. In this case, ion-card is an Angular component, I guess. So, what to do next? I think I have to change my beforeEach, addining some config. Can anyone help?
beforeEach(() => TestBed.configureTestingModule({
declarations: [ ContentDetailComponent ],
providers: [
{ provide: NavController, useClass: NavMock }
]})
);
You need to import ionicModule. Add this in configureTestingModule
imports: [
IonicModule,
],
This is a good starting blog for testing Ionic 2 apps.
You need to configure the app with Ionic module root in beforeEach:
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [MyApp]
providers: [
],
imports: [
IonicModule.forRoot(MyApp)
]
}).compileComponents();
}));

Categories