Reputation: 9084
I am making angular application with reactive form, where i have made a nested form array which will get nested on button click.
A clean working example https://stackblitz.com/edit/angular-thhczx , it has static inputs and hence on click over Add new template
, it will add a another nested part and for Add new property
, it will generate another property array..
So you had got the above working example concept right??
I would like to have the same json but not with add button and with dropdown.
Html of dropdown:
<select multiple [(ngModel)]="selectItems" (change)="changeEvent($event)">
<option *ngFor="let template of templates" [value]="template.key">{{template.value}}</option>
</select>
{{selectItems|json}}
<form [formGroup]="form">
<div *ngFor="let item of array">
{{item.value}} is the parent
<div *ngFor="let child of item.templateChild">
{{child.property_name}}
<input type="text" [value]="child.property_name">
</div>
<br><br><br>
</div>
</form>
<br><br><br>
{{form.value|json}}
Templates array: which gives value for dropdown
templates = [
{
key: 1, value: "Template one",
templateOneChild: [
{ property_name: "Property one" },
{ property_name: "Property two" }
]
},
{
key: 2, value: "Template two",
templateTwoChild: [
{ property_name: "Property three" },
{ property_name: "Property four" },
{ property_name: "Property five" }
]
},
{
key: 3, value: "Template three",
templateThreeChild: [
{ property_name: "Property six" },
{ property_name: "Property seven" }
]
}
]
Also made a stackblitz link for the above https://stackblitz.com/edit/angular-1sg5cv
Here if i select the option template one
and template two
(as the selectbox is multi select) from the dropdown then i am expecting the output as,
"template_details" : [
{ "template_name": "Template One",
"template_data" : [{"property_one": "", "property_two":""}]
},
{ "template_name": "Template Two",
"template_data" : [{"property_three": "", "property_four":"",
"property_five":""}]
}
]
If you click over the two options of template one and two you can see that you will get two
and three
input boxes respectively...
I wish to generate the input boxes with property names automatically under each template on selection of dropdown values..
So in simple need to convert dropdown demo as like the static inputs with add button with the same nested json structure.
I kindly request angular experts to help me in generation of input boxes based on property names for the selected template's..
I did my level best in it unable to get the solution please help me to form nested array json on based on selection of dropdown..
Upvotes: 1
Views: 6870
Reputation: 1
try this in ts
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
constructor(private formBuilder: FormBuilder) {
this.myForm = this.formBuilder.group({
selectedItems: [[]],
itemQuestions: this.formBuilder.array([])
});
}itemQuestions: any[] = [
{ question: 'Question 1', options: ['Yes', 'No'] },
{ question: 'Question 2', options: ['Yes', 'No'] },
{ question: 'Question 3', options: ['Yes', 'No'] }
];
generateItemQuestions(): FormGroup[] {
const selectedItems = this.myForm.get('selectedItems').value;
return selectedItems.map((item: string) => {
return this.formBuilder.group({
item: [item],
answers: ['', Validators.required]
});
});
}
onSelectionChange() {
const itemQuestions = this.generateItemQuestions();
this.myForm.setControl('itemQuestions',
this.formBuilder.array(itemQuestions));
}
onSubmit() {
if (this.myForm.valid) {
const formValue = this.myForm.value;
console.log(formValue);
// Perform necessary actions with the form data
} else {
// Form is invalid, display error messages or handle accordingly
}
}
in html
<form [formGroup]="myForm" (ngSubmit)="onSubmit()">
<ion-item>
<ion-label>Selected Items</ion-label>
<ion-select formControlName="selectedItems" multiple="true">
<ion-select-option value="Item 1">Item 1</ion-select-option>
<ion-select-option value="Item 2">Item 2</ion-select-option>
<!-- Add other options -->
</ion-select>
</ion-item>
<div formArrayName="itemQuestions">
<div *ngFor="let questionGroup of myForm.get('itemQuestions').controls; let i=index" [formGroupName]="i">
<div>
{{ questionGroup.get('item').value }}
</div>
<div *ngFor="let question of itemQuestions">
<div>{{ question.question }}</div>
<ion-radio-group formControlName="answers">
<ion-item>
<ion-label>Yes</ion-label>
<ion-radio value="Yes"></ion-radio>
</ion-item>
<ion-item>
<ion-label>No</ion-label>
<ion-radio value="No"></ion-radio>
</ion-item>
</ion-radio-group>
</div>
</div>
</div>
<ion-button type="submit">Submit</ion-button>
</form>
Upvotes: 0
Reputation: 57939
@Undefined, you need two different jobs
the first part is the easer. Go step by step, if you select template one, you need some like
this.fb.group({
template_name:"template one",
template_data:this.fb.array([
this.fb.group({
property_one:'',
property_two:''
})
])
})
but you want to do the things dinamically, so, make a function that receive an object and return a FormGroup. As you only need the "value" of the template and the childs, your function can be like
createFormGroup(value:string,children:any[]):FormGroup
{
/*e.g. for template one, you send
value: "Template one",
children: [
{ property_name: "Property one" },
{ property_name: "Property two" }
]
*/
let controls:FormGroup[]=children.map(
(x:any)=>this.fb.group({
[x.property_name]:''
})
)
return this.fb.group({
template_name:value,
template_data:this.fb.array(controls)
})
}
So yet we can create a formGroup for the differents templates and join in a FormArray
changeEvent(e) {
let arrayControls:FormGroup[] = [];
//in this.selectItems we have, e.g. [1,3]
for (let select of this.selectItems) {
//search the template, select will be e.g. 1,3
let template:any=this.templates.find(x=>x.key==select);
switch (+select) {
case 1:
arrayControls.push(this.createFormGroup(template.value,template.templateOneChild));
break;
case 2:
arrayControls.push(this.createFormGroup(template.value,template.templateTwoChild));
break;
case 3:
arrayControls.push(this.createFormGroup(template.value,template.templateThreeChild));
break;
}
}
this.form=this.fb.group({
template_details:this.fb.array(arrayControls);
})
}
See that if all ours children of templates was under a property "children" (not templateOneChild for the first, templateTwoChild for the seconds...) our function becomes in
changeEvent(e) {
let arrayControls:FormGroup[] = [];
//in this.selectItems we have, e.g. [1,3]
for (let select of this.selectItems) {
//search the template, select will be e.g. 1,3
let template:any=this.templates.find(x=>x.key==select);
arrayControls.push(this.createFormGroup(template.value,template.children));
}
this.form=this.fb.group({
template_details:this.array(arrayControls);
})
}
Well you have the "form" created, now is time to show it. The form is like
<div *ngIf="form">
<form [formGroup]="form">
<div formArrayName="template_details">
<div *ngFor="let item of details.controls;let i=index" [formGroupName]="i">
<input formControlName="template_name">
<div formArrayName="template_data">
<div *ngFor="let child of item.get('template_data').controls;let j=index" [formGroupName]="j">
<input formControlName="??????????">
</div>
</div>
</div>
</div>
</form>
</div>
Yes, we have a problem, we don't know the "formControlName" of the inner formArray. One solution is have a variable "controlsName" that will be an array of array, so, if e.g. we choose 1 and 3 template our controlsName was like
controlsName=[
["property_one","property_two"],
["property_six",property_seven"]
]
Well, again make a function that return an array of strings with the names of the properties. it's a simply version of our createFormGroup, receive "children" and return an array of strings.
getControlNames(children:any[]):string[]
{
let controlNames:string[]=children.map(x=>x.property_name);
return controlNames;
}
Well, in changeEvent we call to this function after call to createFormGroup
changeEvent(e) {
let arrayControls:FormGroup[] = [];
let controlsName:string[] = []; //<--add this line
for (let select of this.selectItems) {
let template:any=this.templates.find(x=>x.key==select);
switch (+select) {
case 1:
arrayControls.push(this.createFormGroup(template.value,template.templateOneChild));
controlsName.push(this.getControlNames(template.templateOneChild)); //<--and this
break;
... idem with case 2 and case 3...
}
}
this.controlsName=controlsName; //<--give value to name first
//then create the form
this.form=this.fb.group({
template_details:this.fb.array(arrayControls);
})
After this, replace the < input formControlName="??????????" > by
<input [formControlName]="controlsName[i][j]">
See that we use [formControlName] (not formControlName) because is an evaluated expression.
See the stackblitz here
Upvotes: 3
Reputation: 359
I am not sure about your question.You want to dynamically add controls using json.
Reference link : https://angular.io/guide/dynamic-form
Working example : https://stackblitz.com/edit/angular-srpk3w
Replace your files with the below code :
app.component.html
<select multiple [(ngModel)]="selectItems" (change)="changeEvent($event)">
<option *ngFor="let template of templates" [value]="template.key">{{template.value}}</option>
</select>
{{selectItems|json}}
<div *ngIf="form">
<form [formGroup]="form">
<div *ngFor="let item of array">
{{item.value}} is the parent
<div *ngFor="let child of item.templateChild; index as i">
{{child.property_name}}
<input type="text" formControlName="{{child.property_name.split(' ').join('_')}}" [value]="child.property_name" >
</div>
<br><br><br>
</div>
</form>
</div>
<br><br><br>
{{form.value|json}}
app.component.ts
import { Component } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
array: any[] = [];
selectItems: any;
form: FormGroup;
templates = [
{
key: 1, value: "Template one",
templateChild: [
{ property_name: "Property one" },
{ property_name: "Property two" }
]
},
{
key: 2, value: "Template two",
templateChild: [
{ property_name: "Property three" },
{ property_name: "Property four" },
{ property_name: "Property five" }
]
},
{
key: 3, value: "Template three",
templateChild: [
{ property_name: "Property six" },
{ property_name: "Property seven" }
]
}
]
changeEvent(e) {
this.array = [];
for (let select of this.selectItems) {
this.array.push(this.templates[select-1])
this.form=this.getFormValue(this.array);
}
}
getFormValue(array){
let group: any = {};
array.forEach(r=>{
r.templateChild.forEach((t,index)=>{
group[t.property_name.replace(/ /g, "_")]= new FormControl(t.property_name);
})
})
return new FormGroup(group);;
}
}
Upvotes: 0