Introduction:
I am facing a challenge with nested angular forms that seems simple but is proving to be difficult. The dynamic creation of formGroups and formArrays within multiple components is causing confusion.
I apologize for the lengthy code snippet, but it's the minimal example I could provide to explain my issue clearly.
The parent component is quite straightforward with only two formControls. I pass the form to the tasks component to work with it.
Parent Component
this.intakeForm = this.fb.group({
requestor: ['', Validators.required],
requestJustification: ['', Validators.required]
});
HTML:
<form [formGroup]=“intakeForm”>
<app-tasks
[uiOptions]="uiOptions"
[intakeForm]="intakeForm">
</app-tasks>
</form>
Tasks Component
A method in this component triggers generateTask which creates a new form group.
ngOnInit() {
this.intakeForm.addControl('tasks', new FormArray([]));
}
// Push a new form group to our tasks array
generateTask(user, tool) {
const control = <FormArray>this.intakeForm.controls['tasks'];
control.push(this.newTaskControl(user, tool))
}
// Return a form group
newTaskControl(user, tool) {
return this.fb.group({
User: user,
Tool: tool,
Roles: this.fb.array([])
})
}
HTML:
<table class="table table-condensed smallText" *ngIf="intakeForm.controls['tasks'].length">
<thead>
<tr>
<th>Role(s)</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let t of intakeForm.get('tasks').controls let i = index; trackBy:trackByIndex" [taskTR]="t" [ui]="uiOptions" [intakeForm]="intakeForm" [taskIndex]="i">
</tr>
</tbody>
</table>
TR Component
A method in this component triggers the addRole method which adds the form group.
@Input('taskTR') row;
@Input('ui') ui;
@Input('intakeForm') intakeForm: FormGroup;
// Add a new role
addRole($event, task) {
let t = task.get('Roles').controls as FormArray;
t.push(this.newRoleControl($event))
}
// Return a form group
newRoleControl(role) {
return this.fb.group({
Role: [role, Validators.required],
Action: [null, Validators.required]
})
}
HTML
<td class="col-md-9">
<ng-select [items]="ui.adminRoles.options"
bindLabel="RoleName"
bindValue="Role"
placeholder="Select one or more roles"
[multiple]="true"
[clearable]="false"
(add)="addRole($event, row)"
(remove)="removeRole($event, row)">
</td>
The Query
I'm struggling to add formControlName to my TR Component, specifically on the ng-select element. But when I attempt to do so, it says it needs to be within a formGroup.
As far as I can see, the formGroup is in the tasksComponent and wraps the whole table. So technically, it should be within a formGroup, right?
My ultimate goal is to add formControlName to this input, but I'm finding it challenging to figure out the correct path to achieve this.
Here is an image showing the full form object. The last expanded section, Role, is what this input should be named via formControlName for validation purposes.
https://i.sstatic.net/02U1B.png
Updates
Edit 1 - Changes made according to @Harry Ninh
Tasks Component HTML
<tbody>
<tr *ngFor="let t of intakeForm.get('tasks').controls let i = index; trackBy:trackByIndex" [taskTR]="t" [ui]="uiOptions" [intakeForm]="intakeForm" [taskIndex]="i" [formGroup]="intakeForm"></tr>
</tbody>
TR Component HTML
<td class="col-md-9">
<ng-select [items]="ui.adminRoles.options"
bindLabel="RoleName"
bindValue="Role"
placeholder="Select one or more roles"
[multiple]="true"
[clearable]="false"
formControlName="Roles"
(add)="addRole($event, row)"
(remove)="removeRole($event, row)">
</td>
Result:
ERROR Error: formControlName must be used with a parent formGroup directive.