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

Angular动态表单复制后Radio按钮无唯一Name问题求助

问题:Angular动态表单中Radio按钮全局互斥,无法区段独立选择

我开发的Angular表单里,每个动态生成的区段都包含一组Radio按钮,但当前同一时间只能选中所有区段中的一个Radio按钮,推测是这些Radio按钮的name属性不唯一导致的。

表单核心代码

get anatomicalComplaints(): FormArray {
  return this.childForm.get('dcrr_pc')?.get('pc_anat_loc_compl') as FormArray
}

addAnatomicalComplaints() {
  this.anatomicalComplaints.push(this.createNewAnatomicalComplaint())
}

removeAnatomicalComplaints(index: number) {
  this.anatomicalComplaints.removeAt(index)
}

createNewAnatomicalComplaint() {
  return new FormGroup({
    spinal: new FormControl(''),
    vas: new FormControl(''),
    laterality: new FormControl(''),
    radicular_component: new FormControl(''),
    pc_cas_hist_doc_compl: new FormControl('')
  })
}

自定义app-form-input组件代码

TypeScript代码

import { CommonModule } from '@angular/common';
import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormGroupDirective, AbstractControl, FormArray } from '@angular/forms';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

@Component({
  selector: 'app-form-input',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './form-input.component.html',
  styleUrl: './form-input.component.scss',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => FormInputComponent),
      multi: true
    },
  ],
  viewProviders: [
    {provide: ControlContainer, useExisting: FormGroupDirective}
  ]
})
export class FormInputComponent implements OnInit, ControlValueAccessor, OnDestroy {
  
  @Input() placeholder: string = ''
  @Input() type: string = 'text'
  @Input() label: string = ''
  @Input() required: boolean = false
  @Input() minLength: number = 0
  @Input() labelClassList: string = ''
  @Input() classList: string= ''
  @Input() size: string = 'sm'
  @Input() options: string[] = [];
  @Input() selectOptions: any;
  @Input() controlName : string | null = null
  @Input() indexAt : number = 0
  @Input() formType : string = 'horizontal'
  @Input() inputGroup : boolean = false

  protected onTouched: any = () => {}

  private onChange: any = () => {}
  private destroy$ = new Subject<void>()

  control !: AbstractControl

  value: any;

  constructor(public parentForm: FormGroupDirective) {}

  ngOnInit() {
    if (this.controlName && this.parentForm.form) {
      // 获取FormArray对应索引的FormGroup中的目标控件
      const formArray = this.parentForm.form.get('dcrr_pc.pc_anat_loc_compl') as FormArray;
      this.control = formArray.at(this.indexAt).get(this.controlName)!;
      // 同步控件值到组件
      this.control.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(val => {
        this.value = val;
      });
    }
  }

  setRadioId = (index: number) => `${this.controlName}${this.indexAt}${index}`

  writeValue(value: any): void {
    this.value = value
  }

  registerOnChange(fn: any): void {
      this.onChange = fn
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn    
  }

  onInputChange(event: any): void {
    this.value = event.target.value
    this.onChange(this.value);
    // 更新对应FormControl的值
    if(this.control) {
      this.control.setValue(this.value);
    }
  }

  ngOnDestroy(): void {
    this.destroy$.next()
    this.destroy$.complete()
  }
}

组件模板代码

@if(!inputGroup) {
    <div [class]="classList">
        @if(label != '' && formType == 'default') {
            <label>{{label}}</label>
        }
        @if(type == 'text' || type == 'date') {
            <input 
                [type]="type"
                [placeholder]="placeholder"
                [value]="value"
                (input)="onInputChange($event)"
                (blur)="onTouched()"
                class="form-control"
                [class.form-control-sm]="size == 'sm'"
            />
        }
        @if(type == 'radio') {
            @for(option of options; track option; let idx = $index) {
                <div class="form-check form-check-inline">
                    <input class="form-check-input" 
                           [type]="type" 
                           [name]="controlName + indexAt" 
                           [id]="setRadioId(idx)" 
                           [value]="option"
                           [checked]="value === option"
                           (change)="onInputChange($event)">
                    <label class="form-check-label" [for]="setRadioId(idx)">
                        {{option}}
                    </label>
                </div>
            }
        }

        @if(type == 'checkbox') {
            @for(option of options; track option; let idx = $index) {
                <div class="form-check">
                    <input [type]="type" class="form-check-input" [name]="controlName + indexAt">
                    <label class="form-check-label">
                    {{option}}
                    </label>
                </div>
            }
        }

        @if(type == 'select') {
            <select class="form-control" [class.form-control-sm]="size == 'sm'" (input)="onInputChange($event)"
            (blur)="onTouched()">
                <option value="">--Payer--</option>
                <option *ngFor="let o of selectOptions" [value]="o">{{o}}</option>
            </select>
        }

        @if(type == 'textarea') {
            <textarea class="form-control" [class.form-control-sm]="size == 'sm'" rows="5" width="100%"></textarea>
        }
    </div>
}

@if(inputGroup) {
    <div [class]="classList">
        <div class="input-group">
            <span class="input-group-text border-0 bg-transparent">{{label}}</span>
                @if(type == 'text' || type == 'date') {
                    <input 
                        [type]="type"
                        [placeholder]="placeholder"
                        [value]="value"
                        (input)="onInputChange($event)"
                        (blur)="onTouched()"
                        class="form-control"
                        [class.form-control-sm]="size == 'sm'"
                    />
                }
                @if(type == 'select') {
                    <select class="form-control" [class.form-control-sm]="size == 'sm'" (input)="onInputChange($event)"
                    (blur)="onTouched()">
                        <option value="">--Payer--</option>
                        <option *ngFor="let o of selectOptions" [value]="o">{{o}}</option>
                    </select>
                }
        </div>
    </div>
}

尝试过的方案

  • 传入唯一索引
  • 在自定义组件内设置formControlName
  • 配置唯一ID
    以上方案均未解决问题。

复现步骤

  • 加载应用
  • 点击蓝色加号按钮多次添加区段
  • 尝试选择Radio按钮

结果对比

  • 当前实际结果:只能选中全局一个Radio按钮
  • 期望结果:每个区段内可独立选择Radio按钮

解决方案

问题根源是所有区段的Radio按钮使用了相同的name属性,导致浏览器将它们视为同一组,从而全局互斥;同时组件未正确与FormArray中对应索引的FormControl绑定,值同步异常。

  1. 生成唯一Radio组name:将模板中Radio输入框的[name]="controlName"修改为[name]="controlName + indexAt",让每个区段的Radio组拥有唯一名称,浏览器会将它们识别为独立组。

  2. 绑定对应FormControl:在组件ngOnInit中,根据indexAt和controlName从FormArray的对应FormGroup中获取目标控件,订阅其值变化同步到组件;在onInputChange中更新控件值,确保表单数据双向同步。

  3. 同步Radio选中状态:给Radio输入框添加[checked]="value === option",保证选中状态与FormControl的值一致。

内容的提问来源于stack exchange,提问作者Mark Hill

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 00:55:54