angular - "No value accessor for form control path" Error when adding a dynamic input to form using a custom f

ProblemGiven the following form:<form [formGroup]="additional_providers_form"><div

Problem

Given the following form:


<form [formGroup]="additional_providers_form">
        <div formArrayName="providers">
        @for(provider of providers.controls; track provider; let i = $index) {
            <div class="row" [formGroupName]="i">
                <div class="col mb-3">
                    <app-form-control
                        type="text"
                        label="Provider Name"
                        formControlName="provider_name"
                        [required]="true"
                    ></app-form-control>
                </div>
                <div class="col mb-3">
                    <app-form-control
                        type="text"
                        label="Provider License"
                        formControlName="provider_license"
                        [required]="true"
                    ></app-form-control>
                </div>
            </div>
        }
        </div>
        <div class="col text-center">
            <div class="d-grid gap-2">
                <button type="submit" class="btn btn-black btn-lg btn-primary rounded-0 mt-5">
                    Save and Complete
                </button>
            </div>
        </div>
    </form>

I am trying to dynamically create an array list of components for a user to add or remove information for an application. At this point I am working on initializing the form components.

You will see my attempt to initialize the form in the following template, on line 19 and upon initialization create the first element (FormGroup) in the form array.

import { Component } from '@angular/core';
import { FormArray, FormBuilder, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-additional-providers',
  standalone: true,
  imports: [ReactiveFormsModule],
  templateUrl: './additional-providersponent.html',
  styleUrl: './additional-providersponent.scss'
})
export class AdditionalProvidersComponent {
  additional_providers_form!: FormGroup

  constructor(private fb: FormBuilder) {
  }

  ngOnInit() {
    this.additional_providers_form = this.fb.group({
      providers: this.fb.array([this.createProvider()])
    })
  }

  get providers(): FormArray {
   return this.additional_providers_form.get('providers') as FormArray
  }

  addProvider() {
    this.providers.push(this.createProvider())
  }

  removeItem(idx: number) {
    this.providers.removeAt(idx)
  }

  private createProvider() {
    return this.fb.group({
      provider_name: [''],
      provider_license: ['']
    })
  }
}

The formControl objects for the above component are located within the custom component app-form-control given here:

import { CommonModule } from '@angular/common';
import { Component, forwardRef, Input, OnChanges, OnInit, Optional, Self, SimpleChanges } from '@angular/core';
import { ControlContainer, FormControl, FormGroupDirective, FormsModule, NG_VALIDATORS, NG_VALUE_ACCESSOR, NgControl, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-form-control',
  standalone: true,
  imports: [ReactiveFormsModule],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => FormControlComponent),
      multi: true
    }
  ],
  templateUrl: './form-controlponent.html',
  styleUrl: './form-controlponent.scss'
})
export class FormControlComponent implements OnInit, OnChanges {
  @Input() type: string = 'text'
  @Input() label: string = ''
  @Input() placeholder: string = ''
  @Input() isValid: boolean = false
  @Input() required: boolean = false
  
  value: any
  disabled: boolean = false;

  private touched = false
  dirty = false

  onTouched: any = () => {}

  constructor(@Optional() @Self() public ngControl: NgControl) {
    if (this.ngControl != null) {
      this.ngControl.valueAccessor = this
    }
  }

  ngOnInit() { 

  }

  onChange: any = (value: boolean) => {}
  

  ngOnChanges(changes: SimpleChanges): void {
      
  }

  registerOnTouched(fn: any) {
    this.onTouched = fn
  }

  registerOnChange(fn: any) {
    this.onChange = fn;
  }
  
  valueChanged(value: any) {
    this.onChange(value)
    this.markAsTouched()
    this.markAsDirty()
  }

  writeValue(value: any) {
    this.value = value
  }
  
  setDisabledState(isDisabled: boolean) {
    this.disabled = isDisabled
  }

  private markAsTouched(): void {
    if(!this.touched) {
      this.touched = true
      this.onTouched()
    }
  }

  private markAsDirty(): void {
    if(!this.dirty) {
      this.dirty = true
    }
  }
}

with the html template here:

<label 
    class="form-label required"
    [class.required]="required">{{label}}</label>
<input 
    #val
    [type]="type" 
    class="form-control"
    [formControl]="ngControl?.control"
    [class.is-invalid]="dirty && !isValid"
    [class.is-isvalid]="dirty && isValid"
    (change)="valueChanged(val.value)"/>

When trying to initialize the form in this capacity I am receiving the following error:

main.ts:7 ERROR RuntimeError: NG01203: No value accessor for form control path: 'providers -> 0 -> provider_license'.

I know what the error means and why it is happening, however I do not know how to fix it.


Knowns

  • The error is being cause by the custom form element because it doesn't recognize that it is a control component

  • The error is being thrown because the Component is being created dynamically in a for loop


Attempted Solutions

The first potential solution I found was based on an answer to the following question. You will see in the app-form-control template where I added [formControl]="ngControl?.control" and in the typescript I added:

constructor(@Optional() @Self() public ngControl: NgControl) {
    if (this.ngControl != null) {
      this.ngControl.valueAccessor = this
    }
  }

The idea was to use the standalone approach and give it access to the ValueAccessor without worrying about the parent wrapper, but that failed.

Another solution was putting the inputs in as normal without using the custom component, and it doesn't throw the error. I built the custom component to reuse, and would like to avoid the solution not using it.

I have yet to find additional solutions that would help solve my problem.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745354486a4624033.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信