简体   繁体   中英

Nested form control issue in angular v12

So I am filling up a dynamic form from data that I getting from the backend api. So I have this code for

sections: this.formBuilder.array([])

Inside this I again have a nested array which is filled like this

result.formSections.forEach(x => {
    this.sectionsFormArr.push(
      this.formBuilder.group({
        buildingComponents: this.formBuilder.array(
          x.buildingComponents.map(x => this.formBuilder.control(x, x.required ? Validators.required : null))
        ),
      })
    );
  });
});

I have also created a getter for my sections

  get sectionsFormArr(): FormArray {
    return this.filterForm.get('sections') as FormArray;
  }

Here is an image of what I am getting in the sections form array

在此处输入图像描述

Here is my code in the template

  <div formArrayName="sections">
    <div *ngFor="let section of sectionsFormArr.controls; let i=index">
      <div [formGroupName]="i">
        <div formArrayName="buildingComponents">
          <div *ngFor="let bc of section.get('buildingComponents')['controls']; let j=index">
            <div [formGroupName]="j">
              {{bc.value | json}}
              <input formControlName="label" />
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>

I get this error here

Cannot find control with path: 'sections -> 0 -> buildingComponents -> 0 -> label'

Can someone explains why am I getting this? And how to fix it?

So after answer from @Ashot I am able to make the binding work with making it as group but I have to call another component from there which implements ControlValueAccessor.

So instead of binding it with label I need to call this

<app-text-building-component [id]="bc.value.id" [formControl]="bc">
</app-text-building-component>

Here I get error that

No value accessor for form control with unspecified name attribute

If you want so I can add code for my text-bulding-compnent as well

Here is the code of my text-buliding-component

import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { AbstractControl, ControlValueAccessor, FormBuilder, FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator, Validators } from '@angular/forms';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { TextBuildingComponent, TextOptions } from '../../_models/building-components/text-building-component';

@Component({
  selector: 'app-text-building-component',
  templateUrl: './text-building-component.component.html',
  styleUrls: ['./text-building-component.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => TextBuildingComponentComponent),
      multi: true,
    },
    {
      provide: NG_VALIDATORS,
      useExisting: TextBuildingComponentComponent,
      multi: true,
    },
  ],
})
export class TextBuildingComponentComponent implements OnInit, ControlValueAccessor, Validator {
  @Input() viewOnly: boolean = false;

  textComponent: FormGroup;
  public isRequired = false;

  private unsub$ = new Subject();

  get textOptions() {
    return this.textComponent.get('textOptions') as FormGroup
  }

  constructor(private formBuilder: FormBuilder) {
    console.log(11223)
    this.textComponent = this.formBuilder.group({
      ...new TextBuildingComponent(),
      textOptions: formBuilder.group({
        value: { value: null, disabled: false },
      }),
    } as TextBuildingComponent);
  }

  validate(control: AbstractControl): ValidationErrors {
    const isValid = this.textComponent.get('textOptions').get('value').value;
    this.isRequired = control.hasValidator(Validators.required);

    return !isValid && control.hasValidator(Validators.required) ? { invalidForm: { valid: false, message: "textOptions value is required" } } : null;
  }

  ngOnInit(): void {
    if (this.viewOnly) {
      this.textComponent.get('textOptions').disable({ emitEvent: false });
    }
  }

  ngOnDestroy() {
    this.unsub$.next();
    this.unsub$.complete();
  }

  writeValue(obj: TextBuildingComponent): void {
    console.log("obj", obj)
    this.textComponent.patchValue(obj, { emitEvent: false });
  }

  registerOnChange(fn: any): void {
    this.textComponent.valueChanges.pipe(takeUntil(this.unsub$)).subscribe(fn);
  }

  registerOnTouched(fn: any): void { }

  setDisabledState?(isDisabled: boolean): void {
    if (isDisabled) this.textComponent.disable({ emitEvent: false });
    else this.textComponent.enable({ emitEvent: false });
  }
}

and the html code for this is

<form [formGroup]="textOptions">
  <label [appIsRequired]="isRequired">{{ textComponent.value.label}}</label>
  <div class="input-group mb-2">
    <input formControlName="value" type="text" class="form-control"/>
  </div>
</form>

UPDATE

<div formArrayName="sections">
    <div *ngFor="let section of sectionsFormArr.controls; let i=index">
      <div [formGroupName]="i">
        <div formArrayName="buildingComponents">
          <div *ngFor="let bc of section.get('buildingComponents')['controls']; let j=index">
            <app-text-building-component [id]="bc.value.id" [formControlName]="j">
</app-text-building-component>
          </div>
        </div>
      </div>
    </div>
  </div>

and TS(what you had before) :

result.formSections.forEach(x => {
    this.sectionsFormArr.push(
      this.formBuilder.group({
        buildingComponents: this.formBuilder.array(
          x.buildingComponents.map(x => this.formBuilder.control(x, x.required ? Validators.required : null))
        ),
      })
    );
  });
});

you should generate FormGroup instead of FormControl for the lowest level, if you are going to use the label as a FomrmControl property(currently inside the buildingComponents FormArray you have controls, where the value of each control is the object { label: "Text", required: false, ... } ):

result.formSections.forEach(x => {
    this.sectionsFormArr.push(
      this.formBuilder.group({
        buildingComponents: this.formBuilder.array(
          x.buildingComponents.map(x => this.formBuilder.group(x, x.required ? Validators.required : null))
        ),
      })
    );
  });
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM