1

In my parent component, I have a FormGroup with a FormArray, and I want to handle that array in a child component. The parent's HTML does this:

<ng-container [formGroup]="formGroup">
  <app-child formArrayName="theArrayName">

I assumed in the child I would inject the NgControl and then have access:

@Component({
  ...,
  providers: [
    {
        provide: NG_VALUE_ACCESSOR,
        useExisting: forwardRef(() => ChildComponent),
        multi: true
    }
  ]
})
export class ChildComponent implements ControlValueAccessor {
    constructor(private readonly control: NgControl) {
      this.formArray = control.control as FormArray<...>
    }

I always get a null injector saying there's no provider for NgControl.

1
  • is it possible to pass on stackblitz? Commented Aug 15, 2022 at 7:18

4 Answers 4

5

Complementary the Chris Hamilton's answer

<app-child [formArray]="form.get('formArrayNme')"></app-child>

The problem when you mannage a formArray in a child that you pass as input is that you can not use the "typical" constructor of manage FormArrays. You should define a function (*)

    //if is a FormArray of FormControls
    getControl(index:number)
    {
        return this.formArray.at(index) as FormControl
    }
    //if is a FormArray of FormGroup
    getGroup(index:number)
    {
        return this.formArray.at(index) as FormGroup
    }

And use

    <!--if is a formArray of FormControls-->
    <div *ngFor="let control of FormArray;let i=index">
       <input [formControl]=getControl(i)>
    </div>
    
    <!--if is a formArray of FormGroups-->
    <div *ngFor="let group of FormArray;let i=index" [formGroup]="getGroup(i)>
       <input formControlName="prop1">
       <input formControlName="prop2">
    </div>

If we want to use the typical FormArray with formArrayName we need viewProvider the FormGroupDirective and know the name. We can do using a child-control like

    @Component({
      selector: 'child-array',
      templateUrl: 'child-array.html',
      viewProviders:[
         { provide: ControlContainer, useExisting: FormGroupDirective }]
    })
    
    export class ChildComponent  {
      array:FormArray
      arrayName:string="fool"
      @Input() name: string;
      @Input('array') set _(value)
      {
        this.array=value as FormArray
        this.arrayName=Object.keys(this.array.parent.controls)
            .find(key=>this.array.parent.get(key)==this.array)
      }
    }

Now we can use

      <!--if is a formArray of FormControls-->
      <div [formArrayName]="arrayName">
        <div *ngFor="let control of array.controls;let i=index">
          <input [formControlName]="i">
        </div>
      </div>
    
      <!--if is a formArray of FormGroups-->
      <div [formArrayName]="arrayName">
        <div *ngFor="let control of array.controls;let i=index" [formGroupName]="i">
          <input formControlName="prop1">
          <input formControlName="prop2">
        </div>
      </div>

This second approach (in the case of formArray of FormControls) can be see in this stackblitz

(*)I know that some authors use the variable of the loop to get the value of the formControl or the FormGroup

<div *ngFor="let group of formArray.controls" [formGroup]="group">

Unfortunaly, this don't work since Angular 12, because group is only an "AbstractControl". If you has strict mode you received an error saying you that an AbstractControl is not a FormGroup (works in early Angular versions).

Some suggest use the $any, but (personal opinion) is a "ugly work-around" or even de-activate the strict mode (it's a very very very bad idea and this last is not a personal opinion)

Sign up to request clarification or add additional context in comments.

Comments

2

NG_VALUE_ACCESSOR is a provider to create a custom form control not a custom form array. You should define a ChildComponent as a FormControl. Refer this how to do it.

And then you should use FormArray like

<ng-container [formGroup]="formGroup">
  <ng-container formArrayName="theArrayName" >
    <ng-container *ngFor="let control of formGroup.controls.theArrayName.controls; let i = index">
      <app-child [formControlName]="i"></app-child>
    </ng-container>  
  </ng-container>
</ng-container>

1 Comment

So I need the child control to completely handle the array itself. Basically I have the array as input, and then I map that into a 3rd party grid control where each row of the array is a row of the grid. Their control doesn't work with forms though, so when I hit their "done" type button, then I'd convert from their grid rows back to the FormArray.
0

Maybe I'm missing some information, but the simplest way to do that is to just use an input property.

parent ts

export class ParentComponent {
  formGroup = new FormGroup(...);

  get formArray() {
    return this.formGroup.get('theArrayName') as FormArray;
  }
}

child ts

export class ChildComponent implements OnInit {
  @Input() formArray = new FormArray([]);

  ngOnInit() {
    console.log(this.formArray);
  }
}

parent html

<app-child [formArray]="formArray"></app-child>

Comments

0

Julian lin could work, but there is a simple way to do it as well without the ControlValueAccessor if you know you are gonna use a FormGroup instead the array.

So in the ChildComponent you have

export class ChildComponent implements OnInit{
 formGroup: FormGroup
 constructer (private formCtrl: FormGroupDirective){}
 ngOnInit(){
   // only available after ngOnInit
   this.formGroup = formCtrl.form;
 }
}

in the parent component template you would set it something like this

<ng-container *ngFor="let someGroup of formArray.controls">
  <app-child-component [formGroup]="someGroup" ></app-child-component>
</ng-container>

then you can use the parent to still detect if there is an validation error in the child forms.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.