I'm new to angular. Below is the JSON data I'm getting from GET call in postman. I'm trying to build the form using this
data = {
headline: [
{
language: 'en',
headlineText: 'example headline',
},
],
bodyText: [
{
language: 'en',
bodyText: 'example bodytext',
},
],
location: {
name: 'mkontheway',
openingHours: [
{
day: 'Mon-frd',
timing: '10.00PM-9AM',
},
],
address: {
postCode: 'test',
country: 'test',
},
},
};
But here Day and Timing fields are repeating 2 times. I'm not able to get the control of these two fields correctly because of nested form structure. Please someone suggest me the solution to build a complex nested form structure in angular.
Thanks in advance.
Here is the stack blitz link
Here there are nested form groups and form arrays are there. Please suggest me how to achieve this nested form implementation using angular reactive forms
I took the reference from this stack blitz example Reference Stack blitz
CodePudding user response:
You only need create an empty formArray in openingHours in the location
this.createAppForm = this.fb.group({
...
location: this.fb.group({
name: ['', Validators.required],
openingHours: this.fb.array([]), //<--this is an "empty" formAray
address: this.fb.group({
...
}),
}),
});
But (always there're a but) seeing your code you has many repetitions to create the differents FormGroups. And repeat code makes we enter in trouble early.
So we are going to create functions that return the formGroups that compouned your FormGroup
Imagine you has some like
setHeadLine(data: any = null) {
data = data || { language: null, headlineText: null };
return this.fb.group({
headlineText: data.headlineText,
language: data.language,
});
}
setBodyText(data: any = null) {
data = data || { bodyText: null, language: null };
return this.fb.group({
bodyText: data.bodyText,
language: data.language,
});
}
setAddress(data: any = null) {
data = data || { postCode: null, country: null };
return this.fb.group({
postCode: [data.postCode, Validators.required],
country: [data.country, Validators.required],
});
}
setOpeningHour(data: any = null) {
data = data || { day: null, timing: null };
return this.fb.group({
day: [data.day, Validators.required],
timing: [data.timing, Validators.required],
});
}
setLocation(data: any = null) {
console.log(data);
data = data || { name: null, openingHours: null, address: null };
return this.fb.group({
name: [data.name, Validators.required],
openingHours: this.fb.array(
data.openingHours
? data.openingHours.map((x) => this.setOpeningHour(x))
: []
),
address: this.setAddress(data.address),
});
}
See how the "setLocation" call to the function setAddress to create the formGroup adress and how openingHours is a this.fb.array. If data.openingHours is an array, convert the array of objects in an array of formGroups and create the formArray, else return an empty array
Finally you create a function that return your formGroup
setFormGroup(data: any = null) {
data = data || { headline: null, bodyText: null, location: null };
return this.fb.group({
headline: this.fb.array(
data.headline ? data.headline.map((x) => this.setHeadLine(x)) : []
),
bodyText: this.fb.array(
data.bodyText ? data.bodyText.map((x) => this.setBodyText(x)) : []
),
location: this.setLocation(data.location),
});
}
I this way you not repeat code. In the constructor you can write
this.createAppForm = this.setFormGroup(this.data);
And when you add a FormGroup simply call the functions:
addHeadline() {
this.getHeadlineFormData().push(this.setHeadLine())
}
addBodyText() {
this.getBodyTextFormData().push(this.setBodyText())
}
addOpeningHours() {
this.getopeningHoursFormData().push(this.setOpeningHour())
}
Well, (this last is only a suggestion) I think that the code becomes more clear using "getter", so if you replace
//replace
getHeadlineFormData() {
return <FormArray>this.createAppForm.get('headline');
}
//by
get headlineFormData() {
return <FormArray>this.createAppForm.get('headline');
}
//replace
getBodyTextFormData() {
return <FormArray>this.createAppForm.get('bodyText');
}
//by
get bodyTextFormData() {
return <FormArray>this.createAppForm.get('bodyText');
}
//replace
getLocationFormData() {
return <FormArray>this.createAppForm.get('location');
}
//by
get locationFormData() {
return <FormArray>this.createAppForm.get('location');
}
//and replace
getopeningHoursFormData() {
return <FormArray>this.createAppForm.get('location')?.get('openingHours');
}
//by
get openingHoursFormData() {
return <FormArray>this.createAppForm.get('location')?.get('openingHours');
}
You can use this.headlineFormData
instead this.getHeadlineFormData()
-see that using a getter you don't write parentesis. and in html headlineFormData.controls
instead getHeadlineFormData().controls
-again see that using a "getter" you don't write parenthesis.
Your forked stackbliz