Angular2RC5: 不能绑定到“属性 X”,因为它不是“子组件”的已知属性

我有一个小 Angular2项目的基础上 Angular2种子项目,我试图升级到 Angular2 RC5。

我的项目有一些特性,其中一个叫做 home。Home 组件使用名为 create-report-card-form的子组件。我已经声明了家庭和 create-report-card-form组件在 home.module(见下面的代码) ,并得到这个 错误:

未处理的承诺拒绝: 模板解析错误: 无法绑定到 “ currentReportCardCount”,因为它不是 “创建-报告-卡片-表格”。

  1. 如果“创建-报告-卡片形式”是一个角度组件,并且它具有 “ currentReportCardCount”输入,然后验证它是否是此模块的一部分。

项目结构

-app
- app.module.ts
- app.component.ts
- +home
- home.module.ts
- home.component.ts
- home.component.html
- create-report-card-form.component.ts
- create-report-card-form.component.html
- +<other "features">
- shared
- shared.module.ts

家,模块

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {ReactiveFormsModule} from '@angular/forms';


import { SharedModule } from '../shared/shared.module';
import { DataService } from '../shared/services/index';
import { HomeComponent } from './home.component';
import { CreateReportCardFormComponent } from './create-report-card-form.component';


@NgModule({
imports: [CommonModule, SharedModule, ReactiveFormsModule],
declarations: [HomeComponent, CreateReportCardFormComponent],
exports: [HomeComponent, CreateReportCardFormComponent],
providers: [DataService]
})


export class HomeModule { }

应用程序模块

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { APP_BASE_HREF } from '@angular/common';
import { RouterModule } from '@angular/router';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
import { routes } from './app.routes';


import { AboutModule } from './+about/about.module';
import { HomeModule } from './+home/home.module';
import {TestModule} from './+test/test.module';
import {VoteDataEntryModule} from './+vote-data-entry/vote-data-entry.module';
import { SharedModule } from './shared/shared.module';


@NgModule({
imports: [BrowserModule, HttpModule, RouterModule.forRoot(routes), AboutModule, HomeModule,
TestModule, VoteDataEntryModule, SharedModule.forRoot()],
declarations: [AppComponent],
providers: [{
provide: APP_BASE_HREF,
useValue: '<%= APP_BASE %>'
}],
bootstrap: [AppComponent]


})


export class AppModule { }

Create-report-card-form. Component. ts

import { Component, Input, Output, EventEmitter, OnInit} from '@angular/core';
import { FormBuilder, FormGroup, FormControl } from '@angular/forms';
// import { Dialog, Dropdown, SelectItem, Header, Footer, Messages, Message } from 'primeng/primeng';
import { SelectItem, Message } from 'primeng/primeng';


import {ReportCard, ReportCardDataSource} from '../shared/index';
import {CREATE_REPORT_CARD_FORM_HEADING, EDIT_REPORT_CARD_FORM_HEADING} from './constants';


@Component({
moduleId: module.id,
selector: 'create-report-card-form',
templateUrl: 'create-report-card-form.component.html'
})
export class CreateReportCardFormComponent implements OnInit {


@Input() public reportCardDataSourcesItems: SelectItem[];
@Input() public reportCardYearItems: SelectItem[];
@Input() errorMessages: Message[];


@Output() reportCardCreated = new EventEmitter<ReportCard>();
@Output() editReportCardFormValueChanged = new EventEmitter<ReportCard>();


public editReportCardForm: FormGroup;
private selectedReportCardDataSourceIdControl: FormControl;
private selectedReportCardYearControl: FormControl;


// TODO: remove this hack for resetting the angular 2 form once a real solution is available (supposedly in RC5)
private isFormActive: boolean = true;
private formHeaderString: string = CREATE_REPORT_CARD_FORM_HEADING;
private formDialogVisible: boolean = false;
private isCreatingNewReportCard = false;  // false implies that we are updating an existing report card


constructor(private fb: FormBuilder) {
}


configureForm(selectedReportCard: ReportCard, createNewReport: boolean) {
this.isCreatingNewReportCard = createNewReport;


this.resetForm();


this.selectedReportCardDataSourceIdControl.updateValue(selectedReportCard.reportCardDataSource.reportCardSourceId);


this.selectedReportCardYearControl.updateValue(selectedReportCard.reportCardYear);


if (createNewReport) {
this.formHeaderString = CREATE_REPORT_CARD_FORM_HEADING;
} else {
// updating an existing report card
this.formHeaderString = EDIT_REPORT_CARD_FORM_HEADING +
selectedReportCard.reportCardYear + ' ' + selectedReportCard.reportCardDataSource.reportCardSourceName;
}


this.editReportCardForm.valueChanges.subscribe(data => this.onFormValueChanged(data));
}


customGroupValidator(reportCardDataSourceIdControl: FormControl, reportCardYearControl: FormControl,
isCreatingNewReportCard: boolean) {
return (group: FormGroup): { [key: string]: any } => {


// missing data error ...
if (!reportCardDataSourceIdControl.value || !reportCardYearControl.value) {
return { 'requiredDataError': 'Report card year AND provider must be selected.' };
}


// invalid data error ...
if (isCreatingNewReportCard) {
if (!reportCardDataSourceIdControl.touched || !reportCardYearControl.touched) {
return { 'requiredDataError': 'Report card year AND provider must be selected.' };
}
} else {
if (!reportCardDataSourceIdControl.touched && !reportCardYearControl.touched) {
return { 'requiredDataError': 'Report card year OR provider must be selected.' };
}
}


// return null to indicate the form is valid
return null;
};
}


hideFormDialog() {
this.formDialogVisible = false;
}


showFormDialog() {
// hide any previous errors
this.errorMessages = [];
this.formDialogVisible = true;
}


createForm() {
// by default, configure the form for new report card creation by setting
// the initial values of both dropdowns to empty string
this.selectedReportCardDataSourceIdControl = new FormControl('');
this.selectedReportCardYearControl = new FormControl('');


this.editReportCardForm = this.fb.group({
selectedReportCardDataSourceIdControl: this.selectedReportCardDataSourceIdControl,
selectedReportCardYearControl: this.selectedReportCardYearControl
}, {
validator: this.customGroupValidator(this.selectedReportCardDataSourceIdControl, this.selectedReportCardYearControl,
this.isCreatingNewReportCard),
asyncValidator: this.duplicateReportCardValidator.bind(this)
});
}


duplicateReportCardValidator() {
return new Promise(resolve => {


if ((this.errorMessages) && this.errorMessages.length === 0) {
resolve({ uniqueReportCard: true });
} else {
resolve(null);
}
});
}


showError(errorMessages: Message[]) {
this.errorMessages = errorMessages;
}


ngOnInit() {
this.createForm();
}


onEditReportCardFormSubmitted() {


let newReportCard = this.getReportCard(
this.selectedReportCardDataSourceIdControl.value,
this.selectedReportCardYearControl.value,
this.reportCardDataSourcesItems
);


this.reportCardCreated.emit(newReportCard);
}


resetForm() {
this.createForm();
this.isFormActive = false;
setTimeout(() => this.isFormActive = true, 0);
}


onFormValueChanged(data: any) {
let newReportCard = this.getReportCard(
this.selectedReportCardDataSourceIdControl.value,
this.selectedReportCardYearControl.value,
this.reportCardDataSourcesItems
);


this.editReportCardFormValueChanged.emit(newReportCard);
}


private getReportCard(reportCardDataSourceIdString: string, reportCardYearString: string,
reportCardDataSourceItems: SelectItem[]): ReportCard {


let selectedReportCardYear: number = Number(reportCardYearString);
let selectedProviderReportCardId: number = Number(reportCardDataSourceIdString);


let selectedProviderReportCardName: string = 'Unknown Report Card';


for (var i = 0; i < this.reportCardDataSourcesItems.length; i++) {
var element = this.reportCardDataSourcesItems[i];
if (Number(element.value) === selectedProviderReportCardId) {
selectedProviderReportCardName = element.label;
break;
}
}


let reportCard: ReportCard = new ReportCard();


reportCard.reportCardYear = selectedReportCardYear;


reportCard.reportCardDataSource = new ReportCardDataSource(
selectedProviderReportCardId,
selectedProviderReportCardName
);


return reportCard;
}
}

Create-report-card-form. Component ent.html

<p-dialog header={{formHeaderString}} [(visible)]="formDialogVisible" [responsive]="true" showEffect="fade "
[modal]="true" width="400">
<form *ngIf="isFormActive" [formGroup]="editReportCardForm" (ngSubmit)="onEditReportCardFormSubmitted()">
<div class="ui-grid ui-grid-responsive ui-fluid " *ngIf="reportCardDataSourcesItems ">
<div class="ui-grid-row ">
<p-dropdown [options]="reportCardDataSourcesItems" formControlName="selectedReportCardDataSourceIdControl" [autoWidth]="true"></p-dropdown>
</div>
<div class="ui-grid-row ">
<p-dropdown [options]="reportCardYearItems" formControlName="selectedReportCardYearControl" [autoWidth]="true"></p-dropdown>
</div>
<div class="ui-grid-row ">
<p-messages [value]="errorMessages"></p-messages>
</div>
<footer>
<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix ">
<button type="submit" pButton icon="fa-check "
[disabled]="!editReportCardForm?.valid" label="Save "></button>
</div>
</footer>
</div>
</form>
</p-dialog>

家庭,组件

import { Component, OnInit, ViewChild } from '@angular/core';
// // import { REACTIVE_FORM_DIRECTIVES } from '@angular/forms';
// import {ROUTER_DIRECTIVES} from '@angular/router';
// import { InputText, Panel, SelectItem, Message, Growl, Dialog, DataTable, Column, Header, Footer, Tooltip } from 'primeng/primeng';
import { Message, SelectItem } from 'primeng/primeng';


import {CreateReportCardFormComponent} from './create-report-card-form.component';
import { ReportCardDataSource, ReportCard, ProviderData, DataService,
DUPLICATE_REPORT_CARD_MESSAGE } from '../shared/index';
import {ReportCardCommands} from './enums';


/**
* This class represents the lazy loaded HomeComponent.
*/
@Component({
moduleId: module.id,
selector: 'sd-home',
templateUrl: 'home.component.html',
styleUrls: ['home.component.css']
//,directives: [CreateReportCardFormComponent]
})


export class HomeComponent implements OnInit {


public growlMessages: Message[] = [];
public createReportCardError: Message[] = [];
public reportCardDataSourcesItems: SelectItem[] = [{ label: 'Select Provider', value: '' }];
public reportCardYearItems: SelectItem[] = [{ label: 'Select Year', value: '' }];
public providerData: ProviderData = new ProviderData();
public displayReportCardDeleteConfirmation: boolean = false;


private isCreatingNewReportCard: boolean = true;
private selectedReportCard: ReportCard;


@ViewChild(CreateReportCardFormComponent)
private createReportCardFormComponent: CreateReportCardFormComponent;


constructor(private dataService: DataService) { }


ngOnInit() {
let reportCardDataSources: ReportCardDataSource[] = this.dataService.getReportCardDataSources();


for (var i = 0; i < reportCardDataSources.length; i++) {
var element = reportCardDataSources[i];
this.reportCardDataSourcesItems.push({ label: element.reportCardSourceName, value: element.reportCardSourceId });


// retrieve data from localStorage if available
this.providerData = this.dataService.getProviderData();
}


// initialize report card years
const minYear: number = 2000;
// TODO: maxYear should be sourced from the server by a service
let maxYear: number = (new Date()).getFullYear();


for (var i = maxYear; i >= minYear; i--) {
this.reportCardYearItems.push({ value: i.toString(), label: i.toString() });
}
}


// Returns the index of the report card in providerData.reportCards that has the same reporCardSourceId
// and reportCardYear as selectedReportCard, or -1 if there is no match.
indexOf(selectedReportCard: ReportCard): number {
return this.providerData.reportCards.findIndex(x =>
x.reportCardDataSource.reportCardSourceId === selectedReportCard.reportCardDataSource.reportCardSourceId &&
x.reportCardYear === selectedReportCard.reportCardYear);
}


onReportCardCreated(newReportCard: ReportCard) {
if (newReportCard) {


if ((this.indexOf(newReportCard) > -1)) {
// attemp to create a duplicate report card; show error
this.setCreateReportCardFromErrorMessage(DUPLICATE_REPORT_CARD_MESSAGE);
} else {
if (this.isCreatingNewReportCard) {
// save new report card
this.createReportCardError = [];
this.createReportCardFormComponent.hideFormDialog();
this.providerData.reportCards.splice(0, 0, newReportCard);


this.createReportCardFormComponent.hideFormDialog();


} else {
// update existing report card
let reportCardToUpdateIndex: number = this.indexOf(this.selectedReportCard);


if (reportCardToUpdateIndex > -1) {
this.providerData.reportCards[reportCardToUpdateIndex].reportCardDataSource.reportCardSourceId
= newReportCard.reportCardDataSource.reportCardSourceId;
this.providerData.reportCards[reportCardToUpdateIndex].reportCardDataSource.reportCardSourceName
= newReportCard.reportCardDataSource.reportCardSourceName;
this.providerData.reportCards[reportCardToUpdateIndex].reportCardYear
= newReportCard.reportCardYear;
}
}
this.dataService.storeProviderData(this.providerData);
this.isCreatingNewReportCard = true;
this.clearCreateReportCardFormErrorMessage();
this.createReportCardFormComponent.hideFormDialog();
}
}
}


editReportCardFormValueChanged(newReportCard: ReportCard) {
if (this.indexOf(newReportCard) === -1) {
// clear duplicate report card error message in 'create report card' dialog
this.clearCreateReportCardFormErrorMessage();
} else {
// set duplicate report card error message
this.setCreateReportCardFromErrorMessage(DUPLICATE_REPORT_CARD_MESSAGE);
}
}


onAddReportCardButtonClicked() {
this.isCreatingNewReportCard = true;
this.createReportCardFormComponent.configureForm(new ReportCard(), this.isCreatingNewReportCard);
this.createReportCardFormComponent.showFormDialog();
}


onReportCardDeleteButtonClicked(reportCard: ReportCard) {
this.reportCardCommandExecute(reportCard, ReportCardCommands.Delete);
}


onReportCardEditButtonClicked(reportCard: ReportCard) {
this.reportCardCommandExecute(reportCard, ReportCardCommands.EditReportCard);
}


onAddVotesRouterLinkClicked(reportCard: ReportCard) {
this.reportCardCommandExecute(reportCard, ReportCardCommands.EditVotes);
}


onReportCardDeleteConfirmButtonClick(isDeleteOk: boolean) {
if (isDeleteOk) {
this.providerData.reportCards.splice(this.providerData.selectedReportCardIndex, 1);
// store updated reportCards in local storage
this.dataService.storeProviderData(this.providerData);
}
this.displayReportCardDeleteConfirmation = false;
}


reportCardCommandExecute(reportCard: ReportCard, command: ReportCardCommands) {
this.providerData.selectedReportCardIndex = this.indexOf(reportCard);
this.selectedReportCard = reportCard;


switch (command) {
case ReportCardCommands.EditVotes:
this.dataService.storeProviderData(this.providerData);
break;
case ReportCardCommands.Delete:
this.displayReportCardDeleteConfirmation = true;
break;
case ReportCardCommands.EditReportCard:
this.isCreatingNewReportCard = false;
this.createReportCardFormComponent.configureForm(reportCard, this.isCreatingNewReportCard);
this.createReportCardFormComponent.showFormDialog();
break;
default:
break;
}
}


private setCreateReportCardFromErrorMessage(message: Message) {
this.createReportCardError = [];
this.createReportCardError.push(message);


this.createReportCardFormComponent.showError(this.createReportCardError);
}


private clearCreateReportCardFormErrorMessage() {
this.createReportCardError = [];
this.createReportCardFormComponent.showError(this.createReportCardError);
}
}

Home. Component. html

<p-growl [value]="growlMessages" sticky="sticky"></p-growl>
<p-dataTable [value]="providerData.reportCards" [paginator]="true" rows="15" [responsive]="true">
<header>
<div>
<h1>Report Cards ({{providerData.reportCards.length}})</h1>
</div>
<button type="button" pButton icon="fa-plus" (click)="onAddReportCardButtonClicked()" label="Add" title="Add new report card"></button>
</header>
<p-column styleClass="col-button">
<template let-reportCard="rowData">
<button type="button" pButton (click)="onReportCardEditButtonClicked(reportCard)" icon="fa-pencil" title="Edit report card"></button>
</template>
</p-column>
<p-column field="reportCardDataSource.reportCardSourceName" header="Report Card" [sortable]="true"></p-column>
<p-column field="reportCardYear" header="Year" [sortable]="true"></p-column>
<p-column header="Votes" [sortable]="false">
<template let-reportCard="rowData">
{{reportCard.votes.length}}
<!--<button type="button" pButton icon="fa-pencil-square" (click)="editVotes(reportCard)" title="Edit votes"></button>-->
<a [routerLink]="['/votes']" (click)="onAddVotesRouterLinkClicked(reportCard)">Edit</a>
</template>
</p-column>
<p-column styleClass="col-button">
<template let-reportCard="rowData">
<button type="button" pButton (click)="onReportCardDeleteButtonClicked(reportCard)" icon="fa-trash" title="Delete report card"></button>
</template>
</p-column>
</p-dataTable>


<create-report-card-form [currentReportCardCount]="providerData.reportCards.length" [reportCardDataSourcesItems]="reportCardDataSourcesItems"
[reportCardYearItems]="reportCardYearItems" (reportCardCreated)=onReportCardCreated($event) (editReportCardFormValueChanged)=editReportCardFormValueChanged($event)>
</create-report-card-form>


<p-dialog header="Confirm Deletion" [(visible)]="displayReportCardDeleteConfirmation" modal="modal" showEffect="fade">
<p>
Delete the following report card and all related data (<strong>NO undo</strong>)?
</p>
<p>
<strong>{{providerData?.reportCards[providerData.selectedReportCardIndex]?.reportCardDataSource?.reportCardSourceName}}</strong><br/>
<strong>{{providerData?.reportCards[providerData.selectedReportCardIndex]?.reportCardYear}}</strong>
</p>
<footer>
<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix">
<button type="button" pButton icon="fa-close" (click)="onReportCardDeleteConfirmButtonClick(false)" label="No"></button>
<button type="button" pButton icon="fa-check" (click)="onReportCardDeleteConfirmButtonClick(true)" label="Yes"></button>
</div>
</footer>
</p-dialog>
199344 次浏览
<create-report-card-form [currentReportCardCount]="providerData.reportCards.length" ...
^^^^^^^^^^^^^^^^^^^^^^^^

In your HomeComponent template, you are trying to bind to an input on the CreateReportCardForm component that doesn't exist.

In CreateReportCardForm, these are your only three inputs:

@Input() public reportCardDataSourcesItems: SelectItem[];
@Input() public reportCardYearItems: SelectItem[];
@Input() errorMessages: Message[];

Add one for currentReportCardCount and you should be good to go.

I fixed it with adding the prefix (attr.) :

<create-report-card-form [attr.currentReportCardCount]="expression" ...

Unfortunately this haven't documented properly yet.

more detail here

I ran into the same error, when I just forgot to declare my custom component in my NgModule - check there, if the others solutions won't work for you.

If you use the Angular CLI to create your components, let's say CarComponent, it attaches app to the selector name (i.e app-car) and this throws the above error when you reference the component in the parent view. Therefore you either have to change the selector name in the parent view to let's say <app-car></app-car> or change the selector in the CarComponent to selector: 'car'

There are multiple possible causes for this error:

1) When you put the property 'x' inside brackets you are trying to bind to it. Therefore first thing to check is if the property 'x' is defined in your component with an Input() decorator

Your html file:

<body [x]="...">

Your class file:

export class YourComponentClass {


@Input()
x: string;
...
}

(make sure you also have the parentheses)

2) Make sure you registered your component/directive/pipe classes in NgModule:

@NgModule({
...
declarations: [
...,
YourComponentClass
],
...
})

See https://angular.io/guide/ngmodule#declare-directives for more details about declare directives.

3) Also happens if you have a typo in your angular directive. For example:

<div *ngif="...">
^^^^^

Instead of:

<div *ngIf="...">

This happens because under the hood angular converts the asterisk syntax to:

<div [ngIf]="...">

Parent component:

Step 1:

app.component.ts (parent class file)

export class AppComponent {
name = "Bruce Waine";
}

app.component.html (parent template file)

<app-child [data]="name"></app-child>   <!-- <app-child> is child component selector -->

Child Component:

Step 2:

child.component.ts (child component class file which has parent input)

import { Component, OnInit, Input } from '@angular/core';
export class ChildComponent implements OnInit {
@Input() data: any; /**** Add this line ****/


constructor() { }


ngOnInit(): void {
}


}

child.component.html (child component template file)

<p>\{\{data}}</p>