Search code examples
angularangular-reactive-formsangular14angular-dependency-injection

Inject parent FormArray into child component in Angular 14


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.


Solution

  • 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)