4

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.

Neil89
  • 101
  • 1
  • 6

3 Answers3

7

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

BrianInPhx
  • 101
  • 1
  • 3
  • you were sent from heaven. I was moving stuff to different module that uses ngrx store (which i am not very familiar with) and i was searching the whole store / observable code for my mistake, when the other module just didn't have the checkbox imported. – Woozar Mar 01 '23 at 19:08
  • @Woozar glad you got it fixed! – BrianInPhx Mar 03 '23 at 07:21
5

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

Owen Kelvin
  • 14,054
  • 10
  • 41
  • 74
  • I pretty much understand what you're saying, Owen, but maybe I'm doing something wrong. My `roles` controls is now defined in my custom component, `app-role-selector`, instead of in my `ng-select`. As a curiosity, searching for a solution, I found `ngDefaultControl` directive, which looks like is working as I expected. – Neil89 Nov 18 '20 at 08:28
  • Can you share the module on which you have declared your component? – Owen Kelvin Nov 18 '20 at 08:48
  • Thank you for saying HOW to figure out the issue. Was facing this in a unit test and it was already complicated enough getting the ViewChild reference to not be undefined. Found this...10 seconds after reading this, unit test completed! – Tim Aagaard May 06 '22 at 18:16
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

  • Thanks for your resposne, Lalit. I understand now what you're doing, actually I tried to do something similar (with no success). The problem is my component is part of a much bigger form with other fields, and I'm trying to respect that structure. Looking for a solution I found the interesting `ngDefaultControl` directive, which do the trick and make my component work as expected. – Neil89 Nov 18 '20 at 08:47