Angular2 RC5:无法绑定到“属性 X”,因为它不是“子组件”的已知属性

Posted

技术标签:

【中文标题】Angular2 RC5:无法绑定到“属性 X”,因为它不是“子组件”的已知属性【英文标题】:Angular2 RC5: Can't bind to 'Property X' since it isn't a known property of 'Child Component' 【发布时间】:2016-12-21 22:58:47 【问题描述】:

我有一个基于 Angular2 Seed 项目的小型 Angular2 项目,我正在尝试升级到 Angular2 RC5。

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

未处理的 Promise 拒绝:模板解析错误:无法绑定到 'currentReportCardCount' 因为它不是 '创建报告卡表格'。

    如果 'create-report-card-form' 是 Angular 组件并且它具有 '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

home.module

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  

app.module

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.html

<p-dialog header=formHeaderString [(visible)]="formDialogVisible" [responsive]="true" showEffect="fade "
    [modal]="true" >
    <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>

home.component.ts

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>

【问题讨论】:

您应该包含来自 create-report-card-form.component.ts 的代码 @Isaac - 添加了来自 create-report-card-form.component.ts 的代码 您也可以发布模板吗?该错误告诉您 create-report-card-form 模板或父组件正在尝试绑定或访问 CreateReportCardFormComponent 组件上似乎不存在的“currentReportCardCount”属性。 @AndrewBabin - 添加了父子组件的代码和模板。 【参考方案1】:

这个错误有多种可能的原因:

1) 当您将属性 'x' 放在括号内时,您会尝试绑定到它。因此首先要检查的是属性“x”是否在您的组件中使用Input() 装饰器定义

您的 html 文件:

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

你的类文件:

export class YourComponentClass 

  @Input()
  x: string;
  ...

(确保你也有括号)

2) 确保您在 NgModule 中注册了组件/指令/管道类:

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

有关声明指令的更多详细信息,请参阅https://angular.io/guide/ngmodule#declare-directives。

3) 如果您的角度指令中有错字,也会发生这种情况。例如:

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

代替:

<div *ngIf="...">

发生这种情况是因为 angular 将星号语法转换为:

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

【讨论】:

救命稻草。我无法收到该错误消息。问题是我没有在模块中注册我的子组件。 发生此错误的另一个原因是当您想为组件使用属性选择器但忘记选择器中的括号时。示例:你想这样做:&lt;tr my-component&gt;&lt;/tr&gt; 但在 @Component 你这样做 selector: 'my-component' 而不是 selector: '[my-component]'... 是的 - 该错误消息可能会产生误导 - 我赞成这个答案,因为它在一般情况下更彻底地回答了这个问题。具体来说,#2 根本不直观,因为错误“无法绑定到‘属性 X’,因为它不是‘子组件’的已知属性”——所以这是一个有用的提示来追踪它。跨度> 【参考方案2】:

如果您使用 Angular CLI 创建组件,比如CarComponent,它会将app 附加到选择器名称(即app-car),当您在父视图中引用组件。因此,您要么必须将父视图中的选择器名称更改为&lt;app-car&gt;&lt;/app-car&gt;,要么将CarComponent 中的选择器更改为selector: 'car'

【讨论】:

有趣的是,小事最能咬你 这个问题的答案我搜了很久!你简单地解决了这个问题!它工作正常!谢谢!【参考方案3】:

当我忘记在 NgModule 中声明我的自定义组件时,我遇到了同样的错误 - 检查那里,如果其他解决方案不适合你。

【讨论】:

【参考方案4】:

我通过添加前缀 (attr.) 来修复它:

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

很遗憾,这还没有正确记录。

更多详情here

【讨论】:

不要这样做。它将阻止该属性被传递给嵌套指令。 @igasparetto 在这种情况下,您可能没有嵌套指令【参考方案5】:
<create-report-card-form [currentReportCardCount]="providerData.reportCards.length" ...
                         ^^^^^^^^^^^^^^^^^^^^^^^^

在您的 HomeComponent 模板中,您尝试绑定到 CreateReportCardForm 组件上不存在的输入。

在 CreateReportCardForm 中,这些是您仅有的三个输入:

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

为 currentReportCardCount 添加一个,您应该可以开始了。

【讨论】:

谢谢!为 currentReportCardCount 添加 @Input() 解决了这个问题。我的错误是假设代码是正确的,只是因为它在 RC4 及之前的版本中工作。 不客气!老实说,我有点惊讶 RC4 会忽略这样的问题。 另外,如果您的组件是共享模块。确保将其添加到 @NgModule 声明和导出数组中。 @ABabin 我也有同样的问题,但您可以在这里查看更多信息:***.com/questions/66429902/… 我想知道是否有人可以帮助我..

以上是关于Angular2 RC5:无法绑定到“属性 X”,因为它不是“子组件”的已知属性的主要内容,如果未能解决你的问题,请参考以下文章

Angular 2 RC 5 - 无法绑定到“...”,因为它不是“...”的已知属性

无法使用 Angular 2 rc5 发出 http 请求

无法绑定到“formGroup”,因为它不是“form”的已知属性

在 IE10 中升级到 Angular2 RC5 后没有提供 PlatformRef 错误

升级到 RC5 后,Angular2 通用错误找不到模块“@angular/compiler/src/template_parser”

styleUrls 未在 angular2 ChildComponent (RC5) 中加载