I'm developing an Angular application with a component for assigning courses to instructors. The component uses reactive forms, and I'm facing an issue related to form controls. The form dynamically generates checkboxes for selecting courses based on data retrieved from an API. When submitting the form, I successfully receive the values, but I encounter the following error:
assign-course-to-instructor.component.ts:56 ERROR Error: Cannot find control with unspecified name attribute at _throwError (forms.mjs:3150:11) at setUpControl (forms.mjs:2933:13) at FormControlDirective.ngOnChanges (forms.mjs:4570:13) at FormControlDirective.rememberChangeHistoryAndInvokeOnChangesHook (core.mjs:3032:14) at callHookInternal (core.mjs:4024:14) at callHook (core.mjs:4055:9) at callHooks (core.mjs:4006:17) at executeInitAndCheckHooks (core.mjs:3956:9) at selectIndexInternal (core.mjs:11780:17) at Module.ɵɵadvance (core.mjs:11763:5)
This is the typescript code
import { AfterViewInit, Component, OnInit } from '@angular/core';
import { FormArray, FormControl, FormGroup } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { concatMap } from 'rxjs/operators';
import { CourseResponse } from 'src/app/models/course-response';
import { DepartmentResponse } from 'src/app/models/department-response';
import { InstructorResponse } from 'src/app/models/instructor-response';
import { DataService } from 'src/app/services/data.service';
@Component({
selector: 'app-assign-course-to-instructor',
templateUrl: './assign-course-to-instructor.component.html',
styleUrls: ['./assign-course-to-instructor.component.css']
})
export class AssignCourseToInstructorComponent implements OnInit {
instructor_resp: InstructorResponse;
deptList: DepartmentResponse[] = [];
courseList: CourseResponse[] = [];
courseForm: FormGroup = new FormGroup({
courses: new FormArray([])
});
constructor(private dataService: DataService, private route: ActivatedRoute) {
this.instructor_resp = {
id: -1,
name: '',
department: {
id: -1,
name: ''
},
dob: new Date(),
gender: '',
userAuth: {
id: -1,
email: '',
password: '',
role: ''
},
courses: []
};
this.courseForm.setControl('courses', new FormArray([]));
}
ngOnInit(): void {
this.dataService.isLoddedIn().pipe(
concatMap(() => this.dataService.getCourseList()),
concatMap((res) => {
this.courseList = res;
return this.dataService.getDepartmentList();
}),
concatMap((res) => {
this.deptList = res;
return this.dataService.getInstructorById(this.route.snapshot.params['id']);
})
).subscribe({
next: (res) => {
this.instructor_resp = res;
this.instructor_resp.dob = new Date(res.dob);
},
error: (err) => {
console.log(err);
},complete:()=>{
this.updateCoursesFormArray();
}
});
}
private updateCoursesFormArray() {
const coursesFormArray = this.courseForm.get('courses') as FormArray;
this.courseList.forEach(() => {
coursesFormArray.push(new FormControl(false));
});
}
getCoursesControl(index: number): FormControl {
const coursesFormArray = this.courseForm.get('courses') as FormArray;
return coursesFormArray.at(index) as FormControl;
}
onSubmit(): void {
console.log(this.courseForm.value)
}
}
HTML CODE
<div class="container">
<div class="instructor-details">
<ul>
<li>Id: {{ instructor_resp.id }}</li>
<li>Name: {{ instructor_resp.name }}</li>
<li>Email: {{ instructor_resp.userAuth.email }}</li>
<li>DOB: {{ instructor_resp.dob | date: 'dd-MM-yyyy' }}</li>
<li>Department: {{ instructor_resp.department.name }}</li>
<li>Gender: {{ instructor_resp.gender }}</li>
</ul>
<div>
<h5>Filter Course using </h5>
<select>
<option [selected]="true" [hidden]="true" [disabled]="true">Select Department</option>
<option *ngFor="let dept of deptList" [value]="dept.id">{{ dept.name }}</option>
</select>
<select>
<option [selected]="true" [hidden]="true" [disabled]="true">Select Semester</option>
<option value="SEM_1">Sem-1</option>
<option value="SEM_2">Sem-2</option>
<option value="SEM_3">Sem-3</option>
<option value="SEM_4">Sem-4</option>
<option value="SEM_5">Sem-5</option>
<option value="SEM_6">Sem-6</option>
<option value="SEM_7">Sem-7</option>
<option value="SEM_8">Sem-8</option>
<option value="SEM_9">Sem-9</option>
<option value="SEM_10">Sem-10</option>
</select>
</div>
</div>
<div class="course-container">
<form [formGroup]="courseForm" (submit)="onSubmit()">
<div>
<h3>Choose Courses from below</h3>
<div class="item" >
<label>Select Courses</label>
<div formArrayName="courses" *ngFor="let course of courseList; let i = index" class="checkbox-item">
<input type="checkbox" [formControl]="getCoursesControl(i)">
{{ course.courseName }}
</div>
</div>
</div>
<button type="submit">Submit</button>
</form>
</div>
</div>
Any insights or suggestions on resolving this issue would be greatly appreciated!
When we have a FormArray we use a getter to get the formArray
get coursesFormArray()
{
return this.form.get('courses') as FormArray
}
When we have a FormArray of FormControls, we use formControlName
.
and when we have a FormArray always is better iterate over formArray.controls
<div formArrayName="courses">
<div *ngFor="let control of coursesFormArray.controls;let i=index">
<input [formControlName]="i">
</div>
</div>
When we iterate over the formArray.controls we need'n worry about when the formArray is created (e.g. your "courseList" can be created before the formArray, so Angular crash)
Sometimes it's util for avoid initial errors use an "*ngIf" like
<form *ngIf="courseForm" [formGroup]="courseForm">
...
</form>