Angular2 RC5: impossibile eseguire il binding a "Property X" poiché non è una proprietà nota di "Child Component"


86

Ho un piccolo progetto Angular2 basato sul progetto Angular2 Seed che sto cercando di aggiornare ad Angular2 RC5.

Il mio progetto ha alcune caratteristiche, una di queste chiamata home. Il componente Home utilizza un componente figlio chiamato create-report-card-form. Ho dichiarato sia la home che i create-report-card-formcomponenti in home.module(vedi codice sotto) e ottengo questo errore :

Rifiuto promessa non gestita: errori di analisi del modello: impossibile eseguire il binding a "currentReportCardCount" poiché non è una proprietà nota di "create-report-card-form".

  1. Se "create-report-card-form" è un componente Angular e ha l'input "currentReportCardCount", verificare che faccia parte di questo modulo.

Struttura del progetto

-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" 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>

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>

2
Dovresti includere il codice da create-report-card-form.component.ts
Isaac

@Isaac - aggiunto il codice da create-report-card-form.component.ts
ebhh2001

Potete pubblicare anche i modelli? L'errore ti dice che il modello create-report-card-form o il componente principale sta tentando di collegarsi o accedere a una proprietà "currentReportCardCount" che non sembra esistere nel componente CreateReportCardFormComponent.
ABabin

@AndrewBabin - aggiunto codice e modello per componente genitore e figlio.
ebhh2001

Risposte:


59
<create-report-card-form [currentReportCardCount]="providerData.reportCards.length" ...
                         ^^^^^^^^^^^^^^^^^^^^^^^^

Nel tuo modello HomeComponent, stai tentando di collegarti a un input sul componente CreateReportCardForm che non esiste.

In CreateReportCardForm, questi sono i tuoi soli tre input:

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

Aggiungine uno per currentReportCardCount e dovresti essere a posto.


Grazie! L'aggiunta di un @Input () per currentReportCardCount ha risolto questo problema. Il mio errore è stato quello di presumere che il codice fosse corretto solo perché funzionava in RC4 e prima.
ebhh2001

3
Prego! Ad essere onesti, sono un po 'sorpreso che RC4 ignori un problema del genere.
ABabin

6
Inoltre, se il tuo componente è un modulo condiviso. Assicurati di aggiungerlo sia alla dichiarazione @NgModule che agli array di esportazione.
Kildareflare

46

L'ho risolto aggiungendo il prefisso (attr.):

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

Purtroppo questo non è stato ancora documentato adeguatamente.

maggiori dettagli qui


26
Non farlo. Impedirà che la proprietà venga trasmessa a direttive annidate.
igasparetto

1
@igasparetto in una situazione come questa, probabilmente non hai direttive annidate all'interno
CodyBugstein

26

Esistono più possibili cause di questo errore:

1) Quando inserisci la proprietà 'x' tra parentesi, stai cercando di vincolarla. Quindi la prima cosa da verificare è se la proprietà 'x' è definita nel tuo componente con un Input()decoratore

Il tuo file html:

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

Il tuo file di classe:

export class YourComponentClass {

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

(assicurati di avere anche le parentesi)

2) Assicurati di aver registrato le classi di componenti / direttive / pipe in NgModule:

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

Vedere https://angular.io/guide/ngmodule#declare-directives per maggiori dettagli sulle direttive di dichiarazione.

3) Succede anche se hai un errore di battitura nella tua direttiva angolare. Per esempio:

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

Invece di:

<div *ngIf="...">

Ciò accade perché sotto il cofano angolare converte la sintassi dell'asterisco in:

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

Salvavita. Non sono riuscito a visualizzare il messaggio di errore .. Il problema era che non avevo registrato il mio componente figlio nel modulo.
Adeel Shekhani

Un altro motivo per cui questo errore potrebbe verificarsi è quando si desidera utilizzare i selettori di attributi per il componente ma si dimenticano le parentesi nel selettore. Esempio: vuoi fare questo: <tr my-component></tr>ma nel @Componentfai questo selector: 'my-component'invece di selector: '[my-component]'...
BoDeX

Sì - quel messaggio di errore può essere fuorviante - ho votato positivamente questa risposta poiché risponde in modo più completo a questa domanda nel caso generale. In particolare, # 2 non è affatto intuitivo in base all'errore "Impossibile eseguire il binding a" Proprietà X "poiché non è una proprietà nota di" Componente figlio "", quindi questo è un suggerimento utile per rintracciarlo.
Tim

10

Se utilizzi Angular CLI per creare i tuoi componenti, diciamo CarComponent, si collega appal nome del selettore (cioè app-car) e questo genera l'errore precedente quando fai riferimento al componente nella vista genitore. Quindi hai di cambiare il nome del selettore nella vista padre per diciamo <app-car></app-car>o cambiare il selettore in CarComponentaselector: 'car'


5
Divertente sono le piccole cose che ti mordono di più
Nico

Ho cercato a lungo la risposta a questa domanda! Hai semplicemente risolto il problema! Funziona bene! Grazie!
SagitSri

7

Mi sono imbattuto nello stesso errore, quando ho appena dimenticato di dichiarare il mio componente personalizzato nel mio NgModule- controlla lì, se le altre soluzioni non funzionano per te.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.