You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular响应式表单提交报错:无法找到未指定名称属性的控件

解决Angular响应式表单动态复选框的"Cannot find control with unspecified name attribute"错误

问题情况

开发Angular讲师分配课程组件,采用响应式表单实现,基于API返回数据动态生成课程选择复选框。提交表单能成功获取值,但控制台抛出以下错误:

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)

TypeScript代码

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代码

<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>

解决思路与修改方案

1. 移除冗余的FormArray初始化

构造函数中已在courseForm初始化时创建了courses FormArray,后续重复调用this.courseForm.setControl('courses', new FormArray([]))属于多余操作,直接删除该行代码。

2. 修正FormArray的模板绑定方式

错误核心原因是formArrayName与*ngFor放在同一元素,且控件绑定逻辑不符合Angular表单规范,修改如下:

步骤1:添加FormArray getter简化访问

在TS类中添加getter,方便模板直接获取FormArray:

get courses(): FormArray {
  return this.courseForm.get('courses') as FormArray;
}

步骤2:调整HTML模板结构

将formArrayName放在父容器,*ngFor移至内部元素,用[formControlName]绑定索引:

<div class="item">
  <label>Select Courses</label>
  <!-- 父容器设置formArrayName -->
  <div formArrayName="courses" class="checkbox-container">
    <!-- 内部循环遍历课程列表和FormArray索引 -->
    <div *ngFor="let course of courseList; let i = index" class="checkbox-item">
      <input type="checkbox" [formControlName]="i">
      {{ course.courseName }}
    </div>
  </div>
</div>

3. 优化:预勾选讲师已分配课程

当前updateCoursesFormArray中所有控件默认设为false,可根据讲师已有课程设置初始值:

private updateCoursesFormArray() {
  const coursesFormArray = this.courseForm.get('courses') as FormArray;
  this.courseList.forEach(course => {
    // 检查课程是否已分配给讲师
    const isAssigned = this.instructor_resp.courses.some(c => c.id === course.id);
    coursesFormArray.push(new FormControl(isAssigned));
  });
}

4. 可选:添加模板守卫避免空值渲染

为防止API数据未加载完成时模板报错,给表单容器添加*ngIf:

<form [formGroup]="courseForm" (submit)="onSubmit()" *ngIf="courseForm && courses.length > 0">
  <!-- 表单内容 -->
</form>

验证效果

完成修改后,控制台错误会消失,表单提交仍能正常获取选中值,同时讲师已有的课程会自动勾选。

内容的提问来源于stack exchange,提问作者Md Asad Ali

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 00:49:52