Search code examples
javascriptangulartypescriptangular-formsangular-formbuilder

Parent is not passing form values to CVA child form group


I have an Angular 15 application (see source code on Stackblitz) that uses reactive forms and a ControlValueAccessor pattern to create a parent form which includes child form groups. When I add a form group and annotate it as a FormGroup in the parent html template, the data is not passed to the child. When I annotate it as a FormControl it is passing and accessing the data of the child form correctly, but I get the error

ERROR Error: control.registerOnChange is not a function

Furthermore I can't access the individual controls inside the FormGroup if I annotate it as a FormControl.

In the code example childGroupForm2 receives the passed values but childGroupForm does not.

My optimal solution would be to annotate the FormGroup as a FormGroup and pass the values from the parent component to the child.

Does anyone know why this is not working with FormGroups but works with FormControl?

Edit for minimal code:

// parent.component.html

<form [formGroup]="parentForm">
   <child-group [formGroup]="childGroupForm"> </child-group>
   <child-group [formControl]="childGroupForm2"> </child-group>
</form>  



// parent.component.ts

get childGroupForm(): FormGroup {
    return this.parentForm.get('childGroupForm') as FormGroup;
}

get childGroupForm2(): FormControl {
    return this.parentForm.get('childGroupForm2') as FormControl;
}

ngOnInit() {
    this.parentForm = this.fb.group({
      childGroupForm: this.fb.group({
        elementInput: 'Test1',
        elementsSelectionInput: 'Test2',
      }),
      childGroupForm2: this.fb.group({
        elementInput: 'Test3',
        elementsSelectionInput: 'Test4',
      }),
    });
}

Solution

  • Your child-group is a custom form control (a component that implements ControlValueAccessor). A Custom form control is "feed" with a FormControl -not with a FormGroup-.

    So your Form in parent should be

    this.parentForm = this.fb.group({
      name: 'Test0',
      childGroupForm: this.fb.control({ //<--are FormControl that "store" an object
        elementInput: 'Test1',
        elementsSelectionInput: 'Test2',
      }),
      childGroupForm2: this.fb.control({ //<--are FormControl that "store" an object
        elementInput: 'Test3',
        elementsSelectionInput: 'Test4',
      }),
    }); 
    

    And you parent like

    <form [formGroup]="parentForm">
      <child-group formControlName="childGroupForm"></child-group>
      <child-group formControlName="childGroupForm2"></child-group>
    </form>
    

    Personal opinion: A custom form control should be used to make some "more complex" that mannage a serie of inputs. You can get the same using a simple component if you use viewProvider like this SO

    An example with a formGroup, you can see the stackblitz

    //child
    @Component({
      selector: 'hello',
      template: `<h1>Hello </h1>
      <div [formGroupName]="group">
        <input formControlName="elementInput"/>
        <input formControlName="elementsSelectionInput"/>
      </div>      `,
      styles: [`h1 { font-family: Lato; }`],
      viewProviders:[{ provide: ControlContainer, useExisting: FormGroupDirective }]
    })
    export class HelloComponent  {
      @Input() group: string;
      
      constructor(){}
    }
    

    Then the parent use

      <form [formGroup]="form">
         <hello [group]="'childGroupForm'"  > </hello>
      </form>
    
      form=new FormGroup({
        childGroupForm:new FormGroup({
          elementInput:new FormControl(),
          elementsSelectionInput:new FormControl()
      
        })
      })
    

    NOTE: In the example I use new Form and new FormGroup instead of formBuilder, but it's the same