Search code examples
angulartypescriptangular4-forms

Angular - Component Binding Issue


I have a component binding issue. I'm pretty sure that I've got a syntax issue, but I'm not sure what I'm doing wrong.

The full source code is available at https://github.com/TheMagnificent11/LunchVoting in the feature/6_ExtendedInputBug branch (https://github.com/TheMagnificent11/LunchVoting/tree/feature/6_ExtendedInputBug). The master and develop branches have working code that doesn't use the extended-input component.

input-error.component.ts

This is the component that displays a form field error.

import { Component, Input } from '@angular/core';

@Component({
    selector: 'input-error',
    template: './input-error.component.html'
})
export class InputErrorComponent {

    @Input()
    errorMessage: string;

    @Input()
    isError: boolean = true;

}

input-error.component.html

<span class="text-danger" *ngIf="isError">
    {{errorMessage}}
</span>

extended-input.component.ts

This is the component combines label, input and validation errors (collection of child components input-error) for a single form field.

import {
    Component,
    Input,
    ContentChildren,
    QueryList
} from '@angular/core';

import { InputErrorComponent } from '../input-error/input-error.component';

@Component({
    selector: 'extended-input',
    template: './extended-input.component.html'
})
export class ExtendedInputComponent {

    @Input()
    inputName: string = '';

    @Input()
    labelText: string = '';

    @Input()
    isError: boolean = false;

    @ContentChildren(InputErrorComponent)
    errors: QueryList<InputErrorComponent>;

}

extended-input.component.html

<div class="form-group" [ngClass]="{'has-error':border border-danger}">
    <label for="{{inputName}}">{{labelText}}</label>
    <ng-content select="input-control"></ng-content>
    <div *ngIf="isError">
        <ng-content select="input-errors"></ng-content>
    </div>
</div>

controls.module.ts

Module that has extended-input component

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

import { ExtendedInputComponent } from './extended-input.component.ts';
import { InputErrorComponent } from './input-error.component.ts';

let components = [
    ExtendedInputComponent,
    InputErrorComponent
];

@NgModule({
    declarations: components,
    imports: [
        CommonModule,
        ReactiveFormsModule
    ]
})
export class ControlsModule {
}

register.component.ts

This the form component that uses the other extended-input component.

import { Component } from '@angular/core';
import {
    FormBuilder,
    FormGroup,
    FormControl,
    Validators
} from '@angular/forms';

@Component({
    selector: 'register',
    templateUrl: './register.component.html'
})
export class RegisterComponent {

    registrationForm: FormGroup;

    givenName: FormControl;

    surname: FormControl;

    constructor(private formBuilder: FormBuilder) {
        this.givenName = new FormControl('', [Validators.required, Validators.maxLength(100)]);
        this.surname = new FormControl('', [Validators.required, Validators.maxLength(100)]);

        this.registrationForm = this.formBuilder.group({
            givenName: this.givenName,
            surname: this.surname,
        });
    }

}

register.component.html

<form [formGroup]="registrationForm" (submit)="onSubmit()">

    <extended-input [inputName]="'givenName'" 
                    [labelText]="'Given Name'"
                    [isError]="givenName.touched && givenName.invalid">
        <input-control>
            <input type="text" formControlName="givenName" />
        </input-control>
        <input-errors>
            <input-error [isError]="givenName.hasError('required')">
                Given Name is required
            </input-error>
        </input-errors>
    </extended-input>

    <!-- The above components should produce HTML similar to this -->
    <div class="form-group">
        <label for="surname">Surname</label>
        <input type="text" formControlName="surname" />
        <div [hidden]="surname.valid || surname.untouched">
            <span class="text-danger" [hidden]="!surname.hasError('required')">
                Surname is required
            </span>
        </div>
    </div>

    <button type="submit" [disabled]="!registrationForm.valid">
        Register
    </button>

</form>

Entry Module

The module that contains register component.

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

import { ControlsModule } from '../controls/controls.module';

import { RegisterComponent } from './register-component';

@NgModule({
    declarations: components,
    imports: [
        CommonModule,
        ReactiveFormsModule,
        ControlsModule
    ]
})
export class EntryModule {
}

Error

Uncaught Error: Template parse errors:
Can't bind to 'inputName' since it isn't a known property of 'extended-input'.
1. If 'extended-input' is an Angular component and it has 'inputName' input, then verify that it is part of this module.
2. If 'extended-input' 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. ("
        <form [formGroup]="registrationForm" (submit)="onSubmit()">

            <extended-input [ERROR ->][inputName]="'givenName'" 
                            [labelText]="'Given Name'"
                 "): ng:///EntryModule/RegisterComponent.html@9:28
Can't bind to 'labelText' since it isn't a known property of 'extended-input'.
1. If 'extended-input' is an Angular component and it has 'labelText' input, then verify that it is part of this module.
2. If 'extended-input' 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. ("

            <extended-input [inputName]="'givenName'" 
                            [ERROR ->][labelText]="'Given Name'"
                            [isError]="givenName.touched && givenName.inv"): ng:///EntryModule/RegisterComponent.html@10:28
Can't bind to 'isError' since it isn't a known property of 'extended-input'.
1. If 'extended-input' is an Angular component and it has 'isError' input, then verify that it is part of this module.
2. If 'extended-input' 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. (""'givenName'" 
                            [labelText]="'Given Name'"
                            [ERROR ->][isError]="givenName.touched && givenName.invalid">
                <input-control>
               "): ng:///EntryModule/RegisterComponent.html@11:28
'input-control' is not a known element:
1. If 'input-control' is an Angular component, then verify that it is part of this module.
2. If 'input-control' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. (""
                            [isError]="givenName.touched && givenName.invalid">
                [ERROR ->]<input-control>
                    <input type="text" formControlName="givenName" />
             "): ng:///EntryModule/RegisterComponent.html@12:16
Can't bind to 'isError' since it isn't a known property of 'input-error'.
1. If 'input-error' is an Angular component and it has 'isError' input, then verify that it is part of this module.
2. If 'input-error' 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. ("
                </input-control>
                <input-errors>
                    <input-error [ERROR ->][isError]="givenName.hasError('required')">
                        Given Name is required
        "): ng:///EntryModule/RegisterComponent.html@16:33
'input-error' is not a known element:
1. If 'input-error' is an Angular component, then verify that it is part of this module.
2. If 'input-error' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. ("
                </input-control>
                <input-errors>
                    [ERROR ->]<input-error [isError]="givenName.hasError('required')">
                        Given Name is requi"): ng:///EntryModule/RegisterComponent.html@16:20
'input-errors' is not a known element:
1. If 'input-errors' is an Angular component, then verify that it is part of this module.
2. If 'input-errors' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. ("input type="text" formControlName="givenName" />
                </input-control>
                [ERROR ->]<input-errors>
                    <input-error [isError]="givenName.hasError('required')">
       "): ng:///EntryModule/RegisterComponent.html@15:16
'extended-input' is not a known element:
1. If 'extended-input' is an Angular component, then verify that it is part of this module.
2. If 'extended-input' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. ("
        <form [formGroup]="registrationForm" (submit)="onSubmit()">

            [ERROR ->]<extended-input [inputName]="'givenName'" 
                            [labelText]="'Given Name'"
 "): ng:///EntryModule/RegisterComponent.html@9:12
    at syntaxError (vendor.js:sourcemap:38524)
    at TemplateParser.parse (vendor.js:sourcemap:49621)
    at JitCompiler._compileTemplate (vendor.js:sourcemap:63824)
    at vendor.js:sourcemap:63743
    at Set.forEach (<anonymous>)
    at JitCompiler._compileComponents (vendor.js:sourcemap:63743)
    at vendor.js:sourcemap:63630
    at Object.then (vendor.js:sourcemap:38513)
    at JitCompiler._compileModuleAndComponents (vendor.js:sourcemap:63629)
    at JitCompiler.compileModuleAsync (vendor.js:sourcemap:63558)

Does anyone know what I've done wrong?


Solution

  • If you are declaring extendedInputComponent and InputErrorComponent in some other module(ControlsModule) than your RegisterComponent is declared(EntryModule) and ControlsModule is only imported in EntryModule, you have to export the Components also you declared in the same ControlsModule.

    ie, your ControlsModule should be like

    import { NgModule } from '@angular/core';
    import { CommonModule } from '@angular/common';
    import { ReactiveFormsModule } from '@angular/forms';
    
    import { ExtendedInputComponent } from './extended-input.component.ts';
    import { InputErrorComponent } from './input-error.component.ts';
    
    let components = [
        ExtendedInputComponent,
        InputErrorComponent
    ];
    
    @NgModule({
        declarations: components,
        imports: [
            CommonModule,
            ReactiveFormsModule
        ],
        exports: components
    })
    export class ControlsModule {
    }