I am trying to create a reactive form with a complex data structure which is doing a lot of things and I would like your help on how to create it. So basically this is the structure of the data I have which I want to use:
[
{
type: 'body',
items: [
{
type: 'text',
id: 1,
value: '',
isCustom: false,
customValue: ''
}
]
}
]
In items
array, there could be 0 or a million items. So if there is at least one, I want to show fields in the template.
So basically there are two fields for each item. One select box
and another is an input
field. The select box will have some items, let's just say some options. All of the options are predefined and when one of them is selected the value
field in items
object gets stored in. In select box
options, there is one option which is Custom
. When that is selected, the value
field is set to custom
, isCustom
is set to true and the input
field shows up. When something is typed in the input
field, the customValue
field is where it is stored in. This is the basic functionality.
Now I can do everything from updating and storing and validation, etc. Except I am unable to do the very first thing which is setup the form control
. I think I have to use Angular's FormArray
here but I am now sure how.
All I need from you is a bare mininum of how to create this form control
based on the data structure I have provided. Because of not able to do this I moved away from Reactive Forms to template-driven forms, which is giving me a headache with the validations.
This is what I tried so far:
TS file:
private formBuilder = new FormBuilder();
form = this.formBuilder.group({
title: new FormControl('', {
validators: [
Validators.required,
FormValidator.nonWhitespace,
Validators.maxLength(80),
],
}),
body: this.formBuilder.array([]),
});
ngOnInit(): void {
if (this.bodyArray.controls.length === 0) {
this.addBodyItem();
}
}
get bodyArray(): FormArray {
return this.form.get('body') as FormArray;
}
createBodyItem(): FormGroup {
// no idea what to do here
}
addBodyItem(): void {
this.bodyArray.push(this.createBodyItem());
}
Template File:
<div formArrayName="body">
// no idea what to loop here
</div>
Please note that I am using the same component for both create and edit mode. So for edit mode I think the form controls
need to be created in ngOnInit
to show the fields.
It would be really big help if someone can help me with this.
Posting this for anyone's reference. The below code sample generates form controls dynamically while iterating through the array of items provided and properties based on it.
app.component.ts :
import { Component,OnInit } from '@angular/core';
import {FormBuilder, FormGroup, FormArray, FormControl, Validators } from '@angular/forms';
import * as uuid from 'uuid';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit{
myForm: FormGroup;
formControls = [
{ type: 'input', label: 'Name', name: 'name', inputType: 'text' },
{ type: 'input', label: 'Email', name: 'email', inputType: 'email' },
{ type: 'select', label: 'Country', name: 'country', options: [
{ label: 'United States', value: 'US' },
{ label: 'Canada', value: 'CA' },
{ label: 'Mexico', value: 'MX' }
]
}
// Add more form controls as needed
];
constructor(private formBuilder: FormBuilder){}
ngOnInit() {
this.myForm = this.formBuilder.group({});
this.formControls.forEach(control => {
const formControl = new FormControl('', Validators.required); // Create form control
this.myForm.addControl(control.name, formControl); // Add controls to myForm
});
}
}
app.component.html:
<form [formGroup]="myForm">
<ng-container *ngFor="let control of formControls">
<div [ngSwitch]="control.type">
<div *ngSwitchCase="'input'">
<label>{{ control.label }}</label>
<input [formControlName]="control.name" [type]="control.inputType">
</div>
<div *ngSwitchCase="'select'">
<label>{{ control.label }}</label>
<select [formControlName]="control.name">
<option *ngFor="let option of control.options" [value]="option.value">{{ option.label }}</option>
</select>
</div>
<!-- Add more cases for other control types as needed -->
</div>
</ng-container>
</form>
Refer sample working stackblitz here.