【问题标题】:Behavior of ngModel vs formControlName for radio elements无线电元素的 ngModel 与 formControlName 的行为
【发布时间】:2021-12-25 00:27:02
【问题描述】:

在使用两种不同的方法验证相同的无线电输入元素时,即。模板驱动的表单和模型驱动的表单,我被这样的场景困住了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
);

从上面的 sn-p 开始,我对两种表单都使用相同的 HTML 结构,但实例数量有所不同。这里的问题是,当验证发生时,对于模板驱动的表单,我只收到一次错误消息(这是预期的场景),但对于模型驱动的表单,我会收到 3 次显示的错误消息!

我的问题是:

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

工作Stackbliz版本

【问题讨论】:

    标签: angular angular-reactive-forms angular-ngmodel form-control angular-template-form


    【解决方案1】:

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

      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 ,在模板驱动的表单中,作为观察者,您在收音机中拥有这么多元素的数组,以及在模型驱动表单中的一个元素的数组,但我找不到关于这个的解决方案

    【讨论】:

    • 感谢@Eliseo 抽出时间来解决这个问题。我已经知道这种替代解决方案(又名“hack”)来防止重复,但是,我们都知道这不是最优的!我打开这个帖子的唯一目的是了解这种行为背后的核心原因并填补我现在的知识空白。再次感谢!
    • 如果在您的form-control.validation.directive- in ngOnInit, before subscribe you write (this.control.statusChanges as any).observers=[]`(console.log(this.control.statusChanges) 中,您会看到“observers”是一个数组,一开始是空的,在一个元素之后和两个元素之后)也只发送一次错误,但我真的不知道是否是“真正的黑客”或会使应用程序崩溃(真的我认为是时候问给 Angular 的人)。
    • 感谢您进一步调试。这对我来说真的很令人沮丧,Eliseo,为此工作了 3 天,没有任何线索,正如你提到的要联系官方人员,我已经在 Twitter 上联系了两个 GDE,让我们看看情况如何。感谢您抽出时间Eliseo,非常感谢那个人!
    • 我想所有的问题都来自 _initObservables 函数(在 FormControl 构造函数中调用),请参阅github 一个简单的“如果”应该可以解决问题,但它超出了我的知识范围。顺便说一句,前段时间-对不起,如果这假设对您的出色工作有一点欺骗-Netanet Basal 写了一篇文章"Make your angular forms errors message magically apear"。 -他使用 valueChanges,而不是状态更改
    • 他是个好人,我的导师。我彻底关注了他的所有文章以及来自 indepth-dev 的其他文章。从这两个来源中学到了很多东西。我的要求更具体一些,因此我无法按原样使用他的解决方案,但使用动态组件显示错误消息的想法肯定来自您提供的链接!
    【解决方案2】:

    @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 末尾的表单指令树中。原因可以在here找到。
    3. 有趣的是,尽管每个NgModel 指令inherently creates a unique FormControl instance,在指令树中只有一个FormControl 实例将被共享same name 的所有NgModel 共享。 Here 是表示将共享单个 FormControl 实例的逻辑:
    if (this.controls[name]) return this.controls[name];
       this.controls[name] = control;
       control.setParent(this);
       control._registerOnCollectionChange(this._onCollectionChange);
       return control;
    

    registerControlresolvedPromise 承诺解决后被调用。它也在那里dir.control 被分配给registerControl 返回的任何东西。

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

    【讨论】:

      猜你喜欢
      • 2019-07-08
      • 2017-05-03
      • 2021-07-07
      • 2016-07-05
      • 2019-05-09
      • 2018-09-29
      • 2021-06-27
      • 2023-03-09
      相关资源
      最近更新 更多