1

I am trying to develop a template for custom Forms. The goal ist, that the Form gets build dynamically from a XML-File (this far I just create the Input-Elements in the App-Component for testing reasons). The Inputs are sometimes nested in different Groups.

Therefor I have a Form-Group Component and a generic Form-Element Component. I implemented the ControlValueAccessor and everything worked as a charm.

But now, as I want to implement different types of custom FormControls, I am trying to convert the Form-Element Component to a generic Wrapper-Component. The Form-Element Component then calls a Child-Component depending on the type of the given Form-Element Model provided by the XML (texture, checkbox, switch, etc.).

Therefor the ControlValueAccessor is now part of the Child-Component ("TextInputComponent").

But after implementing the Child-Component, this architecture doesn't works as expected. When I am adding a value to the Inputfield by typing into it (part of the Child-Component "TextInputComponent") I always get the ERROR TypeError: this.onChange is not a function. I know, that the problem is, that the registerOnChange-Function is not called (-> so Angular is not parsing the components ControlValueAccessor as needed). The funny part is, that the values of the fields in the FormGroup of Angular are still updating as they have to.

How can I get rid of the error?

Thank you for any help!


Form-Group Component - HTML (FormGroup):

<div class="row border">
    <div class="col">
        <p>{{formGroupModel.id}}</p>
        
        <form [formGroup]="curFormGroup">
        
            <ng-container *ngFor="let item of formGroupModel.Elements">
        
                <hyc-formgroup *ngIf="utils.getInstanceType(item) === 'FormGroupModel'"
                                [parentFormGroup]="curFormGroup"
                                [formGroupModel]="item">
                </hyc-formgroup>
        
                <hyc-formelement *ngIf="utils.getInstanceType(item) === 'FormElementModel'"
                                [parentFormGroup]="curFormGroup"
                                [formElementModel]="item">
                </hyc-formelement>
            </ng-container>
        
        </form>
    </div>
</div>

Form-Group Component - TS:

import { Component, forwardRef, Input, OnInit } from "@angular/core";
import { ControlValueAccessor, FormArray, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from "@angular/forms";

import { Utils } from "src/app/UTILS/INSTANCE_TYPE";
import { FormElementModel } from "../form-element/form-element.model";
import { FormGroupModel } from "./form-group.model";

@Component({
  selector: 'hyc-formgroup',
  templateUrl: './form-group.component.html',
  styleUrls: ['./form-group.component.css'],
})

export class FormGroupComponent implements OnInit {
  @Input()  public formGroupModel : any;
  @Input() public parentFormGroup: FormGroup;

  constructor (
    public formBuilder : FormBuilder,
    public utils : Utils 
  ) {}

  ngOnInit(): void {  
    this.initFormGroup();
  }

  initFormGroup() {
    this.parentFormGroup.addControl(
      this.formGroupModel.id, this.formBuilder.group({})
    );
  }

  get curFormGroup() {
    return this.parentFormGroup.get(this.formGroupModel.id) as FormGroup;
  }
}

Form-Element Component - HTML (Wrapper-Element):

<ng-container *ngIf="formElementModel.type == 'text'">
    <hyc-text-input [parentFormGroup]="parentFormGroup"
                    [formElementModel]="formElementModel">
    </hyc-text-input>
</ng-container>

Form-Element Component - TS:

import { Component, Input } from "@angular/core";
import { FormGroup } from "@angular/forms";

@Component({
  selector: 'hyc-formelement',
  templateUrl: './form-element.component.html',
  styleUrls: ['./form-element.component.css'],
})

export class FormElementComponent {
  @Input() parentFormGroup : FormGroup;
  @Input() formElementModel : any;
  public value : any;

  constructor() {}

}

TextInput-Component - HTML (Actual Form Control):

<input  type="text" 
        class="form-control my-1" 
        placeholder="{{formElementModel.id}}" 
        [value]="value" 
        (input)="_onChange( $event )"
        [formControl]="formControlObj"/>

TextInput-Component - TS:

import { Component, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, NG_VALUE_ACCESSOR } from '@angular/forms';

import { Utils } from "src/app/UTILS/INSTANCE_TYPE";

@Component({
  selector: 'hyc-text-input',
  templateUrl: './text-input.component.html',
  styleUrls: ['./text-input.component.css'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: TextInputComponent,
      multi: true
    }
  ]
})
export class TextInputComponent implements OnInit, ControlValueAccessor {
  @Input() formElementModel;
  @Input() parentFormGroup;

  formControlObj;

  public value : any;
  public onChange: (value: any) => void;

  constructor ( 
    public formBuilder : FormBuilder,
    public utils : Utils 
  ) { }

  ngOnInit(): void {
    this.formElementModel.defaultValue ? this.value = this.formElementModel.defaultValue : this.value = null;
    this.formControlObj = this.formBuilder.control( this.value );

    this.parentFormGroup.addControl( this.formElementModel.id, this.formControlObj );
  }

  _onChange( event: Event) {
    const value = (<HTMLInputElement>event.target).value;
    this.onChange(value);
    this.value = value;
  }

  writeValue(value: any): void {
    if(!value || value == null) return
    this.value = value;
  }
  registerOnChange(fn: (value : any) => void): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: any): void {
  }
  setDisabledState?(isDisabled: boolean): void {
  }

}

Form with View of filled FormGroup-Element at the bottom

Error in Detail

Maybe the Component where it all is nested could be also interesting for you:


FormPage-Component - HTML:

<div class="row">
    <div class="col">

        <div class="text-center">
            <h1>{{formNavi.Titel}}</h1>
            <h2> {{formNavi.CurPageObject.Name}}</h2>
            <p> Mit * markierte Felder sind Pflicht</p>
        </div>

        <form [formGroup]="curPageFormGroup">
            <ng-container *ngFor="let item of formNavi.CurPageObject.Elements">
    
                <hyc-formgroup *ngIf="utils.getInstanceType(item) == 'FormGroupModel'"
                                [formGroupName]="item.id"
                                [parentFormGroup]="curPageFormGroup"
                                [formGroupModel]="item">
                </hyc-formgroup>
    
                <hyc-formelement *ngIf="utils.getInstanceType(item) == 'FormElementModel'"
                                [parentFormGroup]="curPageFormGroup"
                                [formElementModel]="item">
                </hyc-formelement>
           
            </ng-container>
        </form>

    </div>
</div>

<pre> {{wrapperFormGroup.value | json}} </pre>

<button (click)="onClickTest()">TEST</button>

<button (click)="formNavi.nextPage()">NEXT PAGE</button>

FormPage-Component - TS:

import { Component, Input, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { Subscription } from "rxjs";
import { NavigationService } from "../services/navigation.service";
import { Utils } from "../UTILS/INSTANCE_TYPE";

@Component({
    selector: "hyc-formpage",
    templateUrl: "./form-page.component.html",
    styleUrls: ["./form-page.component.css"]
})

export class FormPageComponent implements OnInit {
    @Input() wrapperFormGroup : FormGroup;
    curPageValue : Subscription;

    constructor (
        public formNavi : NavigationService,
        public formBuilder : FormBuilder,
        public utils : Utils
    ) { }

    ngOnInit(): void {
   
    }

    get curPageFormGroup() {
        return this.wrapperFormGroup.get(this.formNavi.CurPageObject.ID) as FormGroup;
    }

}
artuk
  • 11
  • 3
  • Having the same exact issue right now, were you ever been able to solve this? – joseatchang Feb 22 '22 at 18:16
  • I switched to Material Design FormElements and therefore hadn’t to deal with the customFormCo teils anymore. So unfortunately no… I didn’t solved this issues :/ – artuk Feb 23 '22 at 19:26
  • I was able to solve this issue with this https://stackoverflow.com/questions/45696761/create-a-reusable-formgroup/46025197#46025197, I had some nested custom controls not recognizing proper form group, using ng-container with formGroup directive + injecting controlContainer and binding it as in that answer solve the issue for me. – joseatchang Feb 24 '22 at 20:12

0 Answers0