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.