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 class="flex flex-col gap-4 border border-solid border-red-300 p-2">
<select #action formControlName="action" class="formBackground"
(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 class="flex flex-row gap-x-2" formGroupName="data">
<p>Message:</p>
<input type="text" formControlName="message" class="formBackground ml-2">
</div>
</div>
<div *ngIf="action.value == 'Media'" formGroupName="data">
<div class="flex flex-row gap-x-2">
<p>Media:</p>
<input type="text" formControlName="mediaName" class="formBackground ml-2">
</div>
<div class="flex flex-row gap-x-2 mt-2">
<p>Type:</p>
<select formControlName="mediaType" class="formBackground ml-2">
<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 class="flex flex-row mt-2 gap-x-2">
<p>Name:</p>
<input type="text" formControlName="commandName" class="formBackground ml-2">
</div>
<div class="flex flex-row mt-2 gap-x-2">
<p>Trigger:</p>
<select formControlName="commandTrigger" class="formBackground ml-2">
<option selected value="Manual">Manual (Default, only option)</option>
</select>
</div>
</div>
<button (click)="delete(i)" class="bg-red-500 text-white rounded-xl self-center p-2">Delete Instruction</button>
</div>
</ng-container>
</ng-container>
<button (click)="addInstruction()" class="bg-blue-500 text-white rounded-xl self-center p-2 mt-1">
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.
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']
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments