Angular nested FormArray Cannot find control with name: '0-1'

327 Views Asked by At

I am pretty new to angular and learning formArray. I have 2 simple lists of roles and rights. For this, I need to plot a checkbox matrix as follows. Also, at the bottom of every column, I need to show the total count of selected rights.

Sample output

following is the component.ts code that I have written to achieve the above logic.

 constructor(private fb: FormBuilder) {
  
  }

  roleRightsForm: FormGroup;
  
  roles = [
    { id: 1, name: 'Admin' },
    { id: 2, name: 'User' },
    // Add more roles here
  ];
  rights = [
    { id: 1, name: 'Create' },
    { id: 2, name: 'Read' },
    { id: 3, name: 'Update' },
    { id: 4, name: 'Delete' },
  ];

  initRoles(): void {

    this.roleRightsForm = this.fb.group({
      roles: this.fb.array([]),
    });
 
 
    const rolesFormArray = this.roleRightsForm.get('roles') as FormArray;
    this.roles.forEach(role => {
      const roleFormGroup = this.fb.group({
        role: [role],
        rights: this.fb.array([])
      });
        
      this.rights.forEach(right=>{
          const rightFormArray = roleFormGroup.get('rights') as FormArray;
          rightFormArray.push( new FormControl(false));
        })

      rolesFormArray.push(roleFormGroup);
      
    });
  }

  get  getRoles(){
    return (this.roleRightsForm.get('roles') as FormArray).controls;
  }

and following is the html code.

<form [formGroup]="roleRightsForm">
    <table>
      <thead>
        <tr>
          <th></th>
          <th *ngFor="let right of rights">{{ right.name }}</th>
        </tr>
      </thead>
      <tbody>
        <tr *ngFor="let roleGroup of getRoles; let i = index" [formGroup]="roleGroup"  >
           
          <td>{{ roleGroup.value.role.name }}</td>

          <td *ngFor="let right of rights; let j = index"  >
            
            <input type="checkbox" [formControlName]="i + '-' + right.id">
          </td>
       
        </tr>
      </tbody>
    </table>
  </form>

But getting the following error.

main.ts:6 ERROR Error: Cannot find control with name: '0-1'
    at _throwError (forms.mjs:3150:11)
    at setUpControl (forms.mjs:2933:13)
    at FormGroupDirective.addControl (forms.mjs:4782:9)
    at FormControlName._setUpControl (forms.mjs:5370:43)
    at FormControlName.ngOnChanges (forms.mjs:5315:18)
    at FormControlName.rememberChangeHistoryAndInvokeOnChangesHook (core.mjs:2948:14)
    at callHookInternal (core.mjs:3940:14)
    at callHook (core.mjs:3971:9)
    at callHooks (core.mjs:3922:17)
    at executeInitAndCheckHooks (core.mjs:3872:9)

Need guidance on what I am doing wrong here. Also, to show the count under every column of selected rights, should I add a property "count" in "roleFormGroup" and on the checkbox change event, increase/decrease the count in the property? or is there a better way?

Sorry, if the questions are too basic. I am from a jQuery background and trying to wrap my head around Angulalr.

1

There are 1 best solutions below

0
Eliseo On

a FormArray can be a FormArray of FormGroups, a FormArray of FormsControls or a FormArray of FormArray

Well you have a FormArray of FormGroups, one of the FormControls in the FormGroup is a FormArray of FormControls.

Always you have a FormArray you should use a "getter" of the formArray. When you have a formArray inside a FormArray you can not use a getter because you need an "index", so you need a function.

  get rolesFormArray() {
    return this.roleRightsForm.get('roles') as FormArray;
  }

  //this function get the formArrayRight at index
  getRightsFormArray(index: number) {
    return this.roleRightsForm.get('roles.'+index+'.rights') as FormArray
    //I use the "dot notation" with "get", but you can also use
    return this.rolesFormArray.at(i).get('rights') as FormArray
  }

Always you use a FormArray you should iterate over FormArray.controls (not over any variable or array)

<!--see the *ngIf, this avoid initials errors when roleRightsForm is undefined
-->
  <form *ngIf="roleRightsForm" [formGroup]="roleRightsForm">
    <!--see that you need say Angular you're using a formArray
        so give value to formArrayName-->
    <table formArrayName="roles">
      <thead>
        <tr>
          <th></th>
          <th *ngFor="let right of rights">{{ right }}</th>
        </tr>
      </thead>
      <tbody>
        <!--see that you iterate over formArray.controls
          and use formGroupName-->
        <tr
          *ngFor="let roleGroup of rolesFormArray.controls; let i = index"
          [formGroupName]="i"
        >
          <td>{{roles[i].name}}</td>
          <!--see that you need say Angular you're using a formArray
              in this case formArrayName is "rights"-->
          <ng-container formArrayName="rights">

            <!--here use the function that return the formArray
                as is a FormArray of FormControls, here not use 
                [formGroupName]-->
            <td
              *ngFor="let right of getRightsFormArray(i).controls; let j = index"
            >
              <!--you use [formControlName]="j"-->
              <input type="checkbox" [formControlName]="j" />
            </td>
          </ng-container>
        </tr>
      </tbody>
    </table>
  </form>

NOTE: if you see the value of the roleRightsForm.value.roles you see that have a value "role" that it's an object, but have no input asociated

a stackblitz