Just want to say that I recognize that there are many SO posts related to "Can't bind to X since it isn't a known property of Y" errors. I've looked at a ton of them, and have found a number of answers which solve the specific problems, but which I've had trouble translating to my case, which I actually think is quite general, and relates to a fundamental misunderstanding of how I should be solving my use case.
I'm creating an Angular (7) app, which I've separated into components
and routes
. The components
are modular pieces (dropdowns, modals, buttons, whatever), and the routes are individual pages in the app. The terming is a little convoluted, because both are technically Angular components. In other words, the structure (within src/
) looks like this:
- app
- components
- dropdown
- dropdown.component.ts
- dropdown.component.html
- dropdown.component.scss
- dropdown.component.spec.ts
- routes
- library
- library.component.ts
- library.component.html
- library.component.scss
- library.component.spec.ts
...
So I have a Library route, which is just an Angular component which imports a Dropdown component and looks like this:
import { Component, OnInit } from '@angular/core';
import { DropdownComponent } from '../../components/dropdown/dropdown.component';
@Component({
selector: 'app-library',
templateUrl: './library.component.html',
styleUrls: ['./library.component.scss']
})
export class LibraryComponent implements OnInit {
pickItem($event) {
console.log($event.item, $event.index);
}
constructor() { }
ngOnInit() {}
}
The relevant Library HTML file:
<div class="library row py4">
<h3 class="typ--geo-bold typ--caps mb5">Style Library</h3>
<div class="mb4" style="max-width: 35rem;">
<p class="typ--geo-bold typ--caps">Dropdowns</p>
<app-dropdown
[items]="['One', 'Two', 'Three']"
(pick)="pickItem($event)"
title="Default dropdown"
></app-dropdown>
<br />
<app-dropdown
[items]="['One', 'Two', 'Three']"
(pick)="pickItem($event)"
title="Inline dropdown"
class="dropdown--inline"
></app-dropdown>
</div>
</div>
The dropdown component is a basic component, following a similar structure. I won't paste it here unless asked, because I'm not sure it'd be additive. (Suffice it to say that it does accept items
as an Input -- relevant to the below error).
This works perfectly in the browser, and builds correctly in production.
When I run my library.components.spec.ts
test, though, I run into the following error:
Failed: Template parse errors:
Can't bind to 'items' since it isn't a known property of 'app-dropdown'.
1. If 'app-dropdown' is an Angular component and it has 'items' input, then verify that it is part of this module.
2. If 'app-dropdown' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.
3. To allow any property add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component. ("
<p class="typ--geo-bold typ--caps">Dropdowns</p>
<app-dropdown
[ERROR ->][items]="['One', 'Two', 'Three']"
(pick)="pickItem($event)"
title="Default dropdown"
Here's the basic Library spec file:
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { LibraryComponent } from './library.component';
describe('LibraryComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [LibraryComponent],
}).compileComponents();
}));
it('should create the component', () => {
const fixture = TestBed.createComponent(LibraryComponent);
const lib = fixture.debugElement.componentInstance;
expect(lib).toBeTruthy();
});
});
Neither the Library component nor the Dropdown component have associated modules. My understanding is that this error may relate to the fact that I haven't imported the Dropdown component into the Library module, or something, but
- A) I'm not sure how to do that,
- B) I'm then not sure how to incorporate that module into the app at large, and
- C) I'm not sure what the value of that is, outside of getting the test to work.
Is there a way to get the test to work without converting these components to modules? Should all route components be modules?
EDIT
Adding The dropdown component, if relevant:
<div
class="dropdown"
[ngClass]="{ open: open }"
tab-index="-1"
[class]="class"
#dropdown
>
<div class="dropdown__toggle" (click)="onTitleClick(dropdown)">
<span class="dropdown__title">{{ finalTitle() }}</span>
<span class="dropdown__icon icon-arrow-down"></span>
</div>
<div *ngIf="open" class="dropdown__menu">
<ul>
<li
*ngFor="let item of items; let ind = index"
class="dropdown__item"
[ngClass]="{ selected: selectedIndex === ind }"
(click)="onItemClick(dropdown, item, ind)"
>
<span class="dropdown__label">{{ item }}</span>
</li>
</ul>
</div>
</div>
And:
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-dropdown',
templateUrl: './dropdown.component.html',
styleUrls: ['./dropdown.component.scss']
})
export class DropdownComponent implements OnInit {
@Input() items: Array<string>;
@Input() public selectedIndex: number = null;
@Input() public title = 'Select one';
@Input() public open = false;
@Input() public class = '';
@Output() pick = new EventEmitter();
constructor() { }
ngOnInit() {}
finalTitle () {
return typeof this.selectedIndex === 'number'
? this.items[this.selectedIndex]
: this.title;
}
onItemClick(dropdown, item, index) {
this._blur(dropdown);
this.selectedIndex = index;
this.open = false;
this.pick.emit({ item, index });
}
onTitleClick(dropdown) {
this._blur(dropdown);
this.open = !this.open;
}
_blur(dropdown) {
if (dropdown && dropdown.blur) { dropdown.blur(); }
}
}