'Angular 8 - FormArray "No value accessor for form control with path"

I'm using ng-select on a form, but now I want to wrap a specific configuration of this on a custom component. The problem is I'm using a FormArray which contains several of this, in the next way:

this.profileForm = this.formBuilder.group(
  {
    ...
    linkedRoles: this.formBuilder.array([])
  }
);

This linkedRoles is populated with the next FormGroup:

let fgRoles: FormGroup;

for (let i = 0; i < this.userRoles.length; i++) {
  fgRoles = this.formBuilder.group({
    activeRole: [{ value: null }],
    roles: [{ value: null }]
  });
  this.linkedRoles.push(fgRoles);
  this.linkedRoles.controls[i].setValue({
    activeRole: this.userRoles[i].role,
    roles: this.extractUserRoles(this.userRoles[i])
  });

A getter is created for simplicity too:

get linkedRoles(): FormArray {
  return <FormArray>this.profileForm.get("linkedRoles");
}

After this, in my template, I was using this composition which was working perfectly:

<form [formGroup]="profileForm">
  ...

  <table>
    ...
    <tbody>
      <tr formArrayName="linkedRoles"
        *ngFor="let usuR of userRoles;
          let idx = index"
      >
        ...
        <td data-label="Roles" [formGroupName]="idx">
          <ng-select #rolesNgSelect
            [hideSelected]="false"
            bindLabel="rol"
            ...
            formControlName="roles"
          >

            <ng-template ng-label-tmp let-item="item">
              ...
            </ng-template>

            <ng-template ng-option-tmp let-item="item">
              ...
            </ng-template>

          </ng-select>
        </td>
      </tr>
    </tbody>
  </table>

</form>

Now, I'm trying to substitute the ng-select by my custom component role-selector, which basically receives all the necessary properties from the ng-select and wraps this in the template. The change in my previous template is as follows:

<tr formArrayName="linkedRoles"
  *ngFor="let usuR of userRoles;
    let idx = index;
    let ctrlRol of linkedRoles.controls"
>
  <td>
    <app-role-selector
      [hideSelected]="false"
      bindLabel="rol"
      ...
      formControlName="roles"
    >
    </app-role-selector>
  </td>
</tr>

With this subtle change, I'm receiving the next error in console:

ERROR Error: No value accessor for form control with path: 'linkedRoles -> 0 -> roles'

Strangest thing is, when I click the unstyled box that appears, it loads and works almost fine. Should I move my formControlName tag to the ng-select inside my component's template?

POSSIBLE SOLUTION

Looking for a solution, I found the interesting ngDefaultControl. Adding this directive to my custom component makes it behave as expected. Now it looks like this:

<app-role-selector
  [hideSelected]="false"
  bindLabel="rol"
  ...
  ngDefaultControl
  formControlName="roles"
>

The only counterside is I don't fully understand why this is necessary and if this is the best solution.



Solution 1:[1]

The error says No value accessor for form control with path: 'linkedRoles -> 0 -> roles'.

To solve this, begin by finding The control with the name roles. This is ng-select. But we know that ng-select has a ValueAccessor... So why would angular complain?

The reason is basically that you have not imported NgSelectModule in the module where you have declared your component.

But why doesn't Angular complain about unknown element? See this link Ivy is not complaining about unknown element inside ng-template #36171. Angular will not complain about the unkown element but will complain about ValueAccessor

Solution 2:[2]

Assuming that you are having ng-select related template inside app-role-selector.

Parent.component.html

// *ngFor="let role of linkedRoles"

<app-role-selector
      [hideSelected]="false"
      bindLabel="rol"
      ...
      [parentFormArray]='role'>
</app-role-selector>

role-selector.component.ts

 export class RoleSelector {

    @Input() parentForm: FormGroup;
}

role-selector.component.html

<form [formGroup]="parentForm"> // ----> this will have the ref of fgRoles (formGroup) 
     <ng-select #rolesNgSelect
            [hideSelected]="false"
            bindLabel="rol"
            ...
            formControlName="roles"> ----> fgRoles.get('roles')
      ...
     </ng-select>
</form>

Now let's understand the below error

ERROR Error: No value accessor for form control with path: 'linkedRoles -> 0 -> roles'

When we add formControlName in <app-role-selector>, it means we are telling angular to treat <app-role-selector> as any other input field. But as then we have to configure the component to act like a input element (i.e. store value and interact with form element).

More details about Custom Value Accessor

Solution 3:[3]

I know this is not the OP's problem but if it helps anyone, I received a similar error, with my setup. I'm in Angular 13, using Angular Material, and a FormArray. Where a form control in my array uses a mat-checkbox.

Simple fix, I forgot to import the 'MatCheckboxModule' into my module. And now no more errors. Definitely misleading for me, as I thought it was a form issue, not a missing module. Hope that helps anyone

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Owen Kelvin
Solution 2 LALIT KANTA DIBYADARSHAN
Solution 3 BrianInPhx