Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trouble navigating an Angular form to access it's controls

Preface:

I am having the hardest time trying to figure out what sounds like an easy process for nested angular forms. I am dealing with a few components here and some of the formGroups and formArrays are being dynamically created and its throwing me off.

Apologies for the large code dump, but its the minimal example I was able to come up with to try and explain my problem.


The parent component is very straight forward as it only has two formControls. I then pass the form to the tasks component to have access to 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

Some method in here will trigger generateTask which creates the 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

Some method in here will trigger the addRole method which will add 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 Question

I need to add formControlName to my TR Component, specifically on the ng-select. However, when I try and add a formControlName, it tells me that it needs to be within a formGroup.

From what I can tell, the formGroup is in the tasksComponent and is wrapping the whole table so its technically within a formGroup?

My end goal is to be able to add the formControlName to this input but I am having a hard time trying to figure out the path to get there.

Here is an image of the full form object. The last expanded section, Role, is what this input should be called via formControlName so that I can perform validation and what not on the control.

enter image description here

Updates

Edit 1 - Changes for @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.

like image 227
SBB Avatar asked Dec 20 '17 19:12

SBB


People also ask

How do I enable form controls in Angular 6?

Angular tells you that it's better for you to use the ways it gives you to disable/enable form controls. You can enable/disable a form control by using the following ways: Instantiate a new FormControl with the disabled property set to true. FormControl({value: '', disabled: true}) .

What is form control Angular?

What are form controls in Angular? In Angular, form controls are classes that can hold both the data values and the validation information of any form element. Every form input you have in a reactive form should be bound by a form control. These are the basic units that make up reactive forms.

What does patchValue do in Angular?

patchValue()linkPatches the value of a control.


2 Answers

You are expected to declare [formGroup]="intakeForm" in the root tag of every component that wraps all formControlName, formGroupName and formArrayName properties. Angular won't try to go up the hierarchy to find that when compiling the code.

like image 175
Harry Ninh Avatar answered Sep 20 '22 19:09

Harry Ninh


In TR Component template, the root element (ie. the <td>) should have [formGroup]="intakeForm", in order to tell Angular the formControlName who is related to.

like image 22
Francesco Colamonici Avatar answered Sep 21 '22 19:09

Francesco Colamonici