无线电元素的 ngModel 与 formControlName 的行为

Behavior of ngModel vs formControlName for radio elements

同时使用两种不同的方法验证相同的无线电输入元素,即。模板驱动形式和模型驱动形式,我坚持使用 ngModel 对于模板驱动形式的场景,我得到 3 个无线电元素的单个控制实例,但对于模型驱动形式,使用 formControlName,我得到 3 个独立的实例。

<!-- template-driven-form.component.html -->
<div class="form-group gender">
  <label for="gender">Select Gender:</label>
  <div class="radio" *ngFor="let gender of genders">
    <input type="radio" name="gender" [value]="gender" ngModel appFormControlValidation validationMsgId="gender" required />
    <label>{{ gender }}</label>
  </div>
</div>

<!-- model-driven-form.component.html -->
<div class="form-group gender">
  <label for="gender">Select Gender:</label>
  <div class="radio" *ngFor="let gender of genders">
    <input type="radio" name="gender" [value]="gender" formControlName="gender" appFormControlValidation validationMsgId="gender" required />
    <label>{{ gender }}</label>
  </div>
</div>
// model-driven-form.component.ts
genders: string[] = ['Male', 'Female', 'Other'];
this.modelForm = new FormGroup({
  gender: new FormControl(null, [Validators.required])
});

// template-driven-form.component.ts
genders: string[] = ['Male', 'Female', 'Other'];

// form-control-directive
(this.control as NgControl).statusChanges.subscribe(
  // returns single instance for 3 radio elements -> template form
  // returns 3 instance for 3 radio elements -> model form
);

根据上面的代码片段,我对两种形式使用相同的 HTML 结构,但数字实例有所不同。这里的问题是验证发生时,对于模板驱动表单,我只收到一次错误消息(这是预期的情况)但是对于模型驱动表单,我收到错误消息显示 3 次!

我的问题是:

  1. 为什么 ngModelformControlName 为相同元素类型生成的实例数不同?
  2. 需要进行哪些更改才能使 formControlName 也 returns 单实例?

工作Stackbliz版本

我创建了一个解决方案,即在添加之前检查是否存在应用程序错误,请参阅动态中的 if (parent.innerHTML.indexOf(componentFactory.selector) < 0)-component.service

  loadComponentIntoNode(
    vcr: ViewContainerRef,
    dynamicItem: DynamicItem,
    parentNode = null
  ): void {
    if (dynamicItem.component) {
      const parent = parentNode || vcr.element.nativeElement;
      const componentFactory =
        this.componentFactoryResolver.resolveComponentFactory(
          dynamicItem.component
        );
      if (parent.innerHTML.indexOf(componentFactory.selector) < 0) {
        const componentRef = vcr.createComponent(componentFactory);
        const newChild =
          componentRef.injector.get(ErrorComponent).elementRef.nativeElement;
        this.renderer.appendChild(parent, newChild);
        (componentRef.instance as DynamicComponent).data = dynamicItem.data;
      }
    }
  }

问题是,如果您检查 statusChange,在模板驱动形式中,作为观察者,您在无线电中拥有如此多元素的数组,在模型驱动形式中具有一个元素数组,但我不能找到解决方案

@AndreiGatej 回答 - https://github.com/indepth-dev/community/discussions/53

描述:
快速调查了一下,其实应该是三个实例。具体来说,来自 this.statusChangeSubscription = this.control?.statusChanges?.subscribe()statusChanges 被订阅了 3 次。如果您有 N 个单选按钮,那么您最终会得到该指令的 N 个实例。这意味着尽管您有 N 个实例,但它们都会注入 相同的 NgControl 个实例(可以是 NgModelFormControlNameFormControlDirective)。而那个 NgControl 实例是,在反应形式的情况下,这个:

gender: new FormControl(null, [Validators.required]),

使用 NgModel 时显然不会发生这种情况的原因是,在使用模板驱动表单时,表单控制指令是即时创建的。这是设置 NgModel 指令的流程:

  1. this.formDirective.addControl(this).
  2. NgModelNgControl 添加到 this tick. The reason for that can be found here 末尾的表单指令树中。
  3. 有趣的是,尽管每个 NgModel 指令 inherently creates a unique FormControl instance, in the directives tree there will be only one FormControl instance which will be shared by all the NgModel which share the same name. Here 都是指示将共享单个 FormControl 实例的逻辑片段:
if (this.controls[name]) return this.controls[name];
   this.controls[name] = control;
   control.setParent(this);
   control._registerOnCollectionChange(this._onCollectionChange);
   return control;

registerControlresolvedPromise promise 完成后被调用。它也在那里 dir.control 被分配给任何 registerControl returns.

因此,指令的 ngOnInit 在承诺(本质上是 Promise.resolve())解析之前 被调用 。在当前 tick 结束时,所有 NgModel 指令将共享同一个 FormControl 实例。这就是它按预期工作的原因。