为什么我的自定义组件内的无线电输入元素无法与响应式表单一起正常工作?

Why is my radio input element inside a custom component not working properly with reactive forms?

我正在尝试编写一个提供可重复使用的自定义单选输入字段的组件。我在 StackBlitz 中创建了一个真正简单的示例,我希望它能传达我正在尝试做的事情。这个简单的应用程序没有按预期工作,我还没有弄清楚原因。会喜欢别人的眼睛。

自定义单选组件的html

<div [formGroup]="frm">
  <label [for]="id">
  <input [id]="id" type="radio" [value]="value" [formControlName]="cn">{{label}}
  </label>
</div>

自定义无线电组件的打字稿

import { Component, Input } from "@angular/core";
import { FormGroup } from "@angular/forms";

@Component({
  selector: 'app-radio',
  templateUrl: './radio.component.html',
  styleUrls: ['./radio.component.css']
})
export class RadioComponent {
  @Input() frm: FormGroup;
  @Input() label: string;
  @Input() cn: string;
  @Input() value: string;
  @Input() id: string;
}

HTML 使用自定义无线电组件的地方

<form [formGroup]="frm">
  <app-radio id="rc1" [frm]="frm" label="Yes" value="Yes" cn="question" ></app-radio>
  <app-radio id="rc2" [frm]="frm" label="No" value="No" cn="question"></app-radio>
</form>
<hr>
<h3>Form Data</h3>
<pre>{{frm.value | json}}

使用自定义无线电组件html 的打字稿

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit  {
  frm: FormGroup;

  constructor(private fb: FormBuilder){}

  ngOnInit() {
     this.frm = this.fb.group({
    question: ['Yes', Validators.required]
  });
  }
}

预期行为

  1. 预计会出现以下初始屏幕:

  2. 当我在初始屏幕后单击“否”单选按钮时,“是”单选按钮未被选中,“否”单选按钮被选中。
  3. 当我在初始屏幕后单击“否”单选按钮时,表单数据显示问题 属性 的值为 "No"。
  4. 随后的选项单击取消选中其他选项并选中单击的选项。
  5. 随后的选项点击会更改问题字段的表单值。

实际行为

  1. 成功
  2. 失败。两个选项都被选中。
  3. 成功
  4. 失败。单击任一选项时似乎没有任何反应。
  5. 失败。单击任一选项时似乎没有任何反应。

其他观察结果 正如我从 Chrome Dev Tool:

截取的屏幕截图所示,我很惊讶在生成的输入元素上找不到 value 和 name 属性

如果有人能帮助我,我将不胜感激。谢谢。

我认为这里发生的是你的两个单选按钮没有连接。我刚刚尝试了您的 Stackblitz 示例,对我来说初始值为是,当我单击否时,两者都被选中。

据我所知,单选按钮需要共享相同的 name 属性 才能连接,W3 schools 也记录了这一点。您可以尝试以下方法,它使用名称 question:

<form [formGroup]="frm">
  <app-radio id="rc1" name="question" [frm]="frm" label="Yes" value="Yes" cn="question" ></app-radio>
  <app-radio id="rc2" name="question" [frm]="frm" label="No" value="No" cn="question"></app-radio>
</form>

老实说:我不太明白您在这里使用自定义组件想要实现的目标。您还可以使用本机单选按钮:

<form [formGroup]="frm">
  <input type="radio" id="rc1" name="question" label="Yes" value="Yes" cn="question" >
  <input type="radio" id="rc2" name="question" label="No" value="No" cn="question">
</form>

注意:我在这里删除了[frm]="frm"

如果您需要自定义 CSS,您也可以覆盖它:

input {
    color: red;
}

编辑:使用 RadioControlValueAccessor

官方 Angular documentation 展示了一个在响应式表单中使用 radioButtons 的示例,类似于您所做的(除了包装 radioButton 输入字段的自定义组件)。

<form [formGroup]="form">
    <input type="radio" formControlName="food" value="beef" > Beef
    <input type="radio" formControlName="food" value="lamb"> Lamb
    <input type="radio" formControlName="food" value="fish"> Fish
</form>

这里他们都使用相同的 formControlName food 到 link radioButtons。在您的问题中,这将是 question 以匹配您的 formControl。

这是更新后的 StackBlitz,我相信它符合您的行为期望。 我添加了 [checked]="frm.get(cn).value === value".

radio.component.html

<div [formGroup]="frm">
  <label [for]="id">
    <input [id]="id" type="radio" [value]="value" [formControlName]="cn" [checked]="frm.get(cn).value === value">{{label}}
  </label>
</div>

我仍在深入挖掘自己,但我怀疑问题是 app.component.html 中的外部 formGroup 指令无法定位由 [=14 创建的嵌套无线电输入=].

为什么我的 Stackblitz 示例不起作用?

为什么我的 Stackblitz 样本没有按预期工作的答案在 RadioControlRegistry 的源代码 (packages/forms/src/directives/radio_control_value_accessor.ts).

  select(accessor: RadioControlValueAccessor) {
    this._accessors.forEach((c) => {
      if (this._isSameGroup(c, accessor) && c[1] !== accessor) {
        c[1].fireUncheck(accessor.value);
      }
    });
  }

  private _isSameGroup(
      controlPair: [NgControl, RadioControlValueAccessor],
      accessor: RadioControlValueAccessor): boolean {
    if (!controlPair[0].control) return false;
    return controlPair[0]._parent === accessor._control._parent &&
        controlPair[1].name === accessor.name;
  }

当您单击未选中的无线电控件时,将调用此 select 函数。它将为具有相同名称 属性 和相同 parent.

的其他无线电控件调用 fireUncheck

最后一部分是问题所在。每个自定义无线电控件都有不同的 parent。 Parent这里指的是一个FormGroupDirective的实例。每个自定义单选控件都包含一个 div,它获取 FormGroupDirective 的一个新实例。 fireUncheck 永远不会被调用。

解法:

如果我能以某种方式使 parent 在这种情况下相等,它就会起作用。

我发现我可以在无线电组件中使用 formControl 指令而不是 formControlName 指令。

我已经创建了 Stackblitz sample 的修改版本。我用 formControl 指令替换了 formGroup 和 formControlName 指令。就像一个魅力。

修改后的代码如下:

自定义单选组件的html

<div>
  <label [for]="id">
  <input [id]="id" type="radio" [value]="value" [formControl]="ctrl">{{label}}
  </label>
</div>

自定义无线电组件的打字稿

import { Component, Input } from "@angular/core";
import { FormControl } from "@angular/forms";

@Component({
  selector: 'app-radio',
  templateUrl: './radio.component.html',
  styleUrls: ['./radio.component.css']
})
export class RadioComponent {
  @Input() ctrl: FormControl;
  @Input() label: string;
  @Input() value: string;
  @Input() id: string;
}

HTML 使用自定义无线电组件的地方

<form [formGroup]="frm">
  <app-radio id="rc1" label="Yes" value="Yes" [ctrl]="frm.get('question')" ></app-radio>
  <app-radio id="rc2" label="No" value="No" [ctrl]="frm.get('question')"></app-radio>
</form>
<hr>
<h3>Form Data</h3>
<pre>{{frm.value | json}}

使用自定义无线电组件html 的打字稿

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit  {
  frm: FormGroup;
  
  constructor(private fb: FormBuilder){}

  ngOnInit() {
     this.frm = this.fb.group({
    question: ['Yes', Validators.required]
  });
  }
}