I'm trying to build a form. This form has the below structure. My users need to be able to create any amount of entries for the instruction fields.
...
this.buttonForm = this.fb.group({
instructions: this.fb.array([
this.fb.group({
action: this.fb.control('', Validators.required),
data: this.fb.group({
message: this.fb.control(""),
mediaName: this.fb.control(""),
mediaType: this.fb.control(""),
commandName: this.fb.control(""),
commandTrigger: this.fb.control("")
})
})
]),
name: this.fb.control("", Validators.required),
dimensions: this.fb.group({
width: this.fb.control(5, [Validators.required, Validators.min(1)]),
height: this.fb.control(5, [Validators.required, Validators.min(1)]),
positionX: this.fb.control(5, [Validators.required, Validators.min(0)]),
positionY: this.fb.control(5, [Validators.required, Validators.min(0)])
})
});
}
...
get instructionAray() {
return this.buttonForm.get("instructions") as FormArray;
}
public addInstruction() {
this.instructionAray.push(this.fb.control(""));
}
Below is the HTML for the component (excluding the non array items).
<ng-container formArrayName="instructions">
<ng-container *ngFor="let instruction of instructionAray.controls; let i = index" [formGroupName]="i">
<div >
<select #action formControlName="action"
(change)="resetInstruction(i, $event.target)">
<option value="Command">Command</option>
<option value="Media">Media</option>
<option value="Message">Message</option>
</select>
<div *ngIf="action.value == 'Message'" >
<div formGroupName="data">
<p>Message:</p>
<input type="text" formControlName="message" >
</div>
</div>
<div *ngIf="action.value == 'Media'" formGroupName="data">
<div >
<p>Media:</p>
<input type="text" formControlName="mediaName" >
</div>
<div >
<p>Type:</p>
<select formControlName="mediaType" >
<option value="soundEffect">Sound Effect</option>
<option value="imageGif">Image / GIF / Static</option>
<option value="video">Video</option>
</select>
</div>
</div>
<div *ngIf="action.value == 'Command'" formGroupName='data'>
<div >
<p>Name:</p>
<input type="text" formControlName="commandName" >
</div>
<div >
<p>Trigger:</p>
<select formControlName="commandTrigger" >
<option selected value="Manual">Manual (Default, only option)</option>
</select>
</div>
</div>
<button (click)="delete(i)" >Delete Instruction</button>
</div>
</ng-container>
</ng-container>
<button (click)="addInstruction()" >
Add Instruction</button>
I'm running into a problem when I try to display the form array data. The first item in the array with the defualt properties displays fine. However, when I add another item to the form array it fails with the below error.
ERROR Error: Cannot find control with path: 'instructions -> 1 -> action'
Angular 11
ButtonFormComponent_ng_container_40_Template button-form.component.html:68
Angular 26
RxJS 6
Angular 23
ButtonFormComponent_Template button-form.component.html:108
Angular 12
BoardComponent_ng_template_3_Template board.component.html:43
Angular 8
openTemplateSheetMenu board.component.ts:52
BoardComponent_div_2_Template__svg_svg_click_1_listener board.component.html:23
Angular 24
BoardComponent_div_2_Template board.component.html:23
Angular 9
BoardComponent_Template board.component.html:20
Angular 2
core.mjs:6412:22
I've been stuck on this for a few days now with no success.
CodePudding user response:
You need to push a FormGroup inside the FormArray instead of FormControl.
like this
public addInstruction() {
this.instructionAray.push(
this.fb.group({
action: this.fb.control('', Validators.required),
data: this.fb.group({
message: this.fb.control(''),
mediaName: this.fb.control(''),
mediaType: this.fb.control(''),
commandName: this.fb.control(''),
commandTrigger: this.fb.control(''),
}),
})
);
}
You should also use the entire html content inside a form tag with the formGroup. like this.
<form [formGroup]="buttonForm">
....
....
</form>
CodePudding user response:
Translating this error to plain English;
You are creating a formArray
and you are supplying a default object formGroup
to it OnInit
. At this point of time you created a formGroup
by default inside your formArray. So in your array you have an object at index 0.
Thus, when you press the addInstruction()
you are pushing a new object to the array. Meaning an object at index 1. That's why the compiler complains about the absence of a control named action
at index 1 when you push the new object by pressing addInstruction()
. In the supplied code above, the addInstruction()
does not create a new object that includes the shape of the object you set by default at index 0. It only pushes an abstract formControl
to the formArray
.
So you need to refactor as follows:
1- Declare a property in your class of type FormArray
named instructions
public instructions: FormArray
2- Rework the addInstruction()
:
addInstruction() {
this.instructions = this.buttonForm.get("instructions") as FormArray;
this.instructions.push(this.createInstructions());
}
3- add a new method named createInstructions()
createInstructions(){
return this.fb.group({
action: this.fb.control('', Validators.required),
data: this.fb.group({
message: this.fb.control(""),
mediaName: this.fb.control(""),
mediaType: this.fb.control(""),
commandName: this.fb.control(""),
commandTrigger: this.fb.control("")
})
})}
4- Remove the get instructionAray()
It is not recommended to call methods that way in your template. For performance reasons use direct properties. Knowing that you have the buttonForm
already in your HTML
you can access your formArray
in the *ngFor
as follows:
this.buttonForm.get('instructions')['controls']