Home > Enterprise >  Inject parent FormArray into child component in Angular 14
Inject parent FormArray into child component in Angular 14

Time:08-15

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.

CodePudding user response:

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>

CodePudding user response:

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>

CodePudding user response:

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.

CodePudding user response:

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)

  • Related