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(); }
}
}