【问题标题】:Dividing a form into multiple components with validation通过验证将表单分成多个组件
【发布时间】:2017-09-02 09:24:44
【问题描述】:

我想创建一个角度为 2 的大表单。但我想创建这个包含多个组件的表单,如下例所示。

应用程序组件

<form novalidate #form1="ngForm" [formGroup]="myForm">
<div>
    <address></address>
</div>
<div>
    <input type="text" ngModel required/>
</div>

<input type="submit" [disabled]="!form1.form.valid" > </form>

地址组件

<div>
<input type="text" ngModel required/> </div>

当我使用上面的代码时,它在我需要的浏览器中可见,但是当我删除地址组件中的文本时,提交按钮没有被禁用。
但是当我删除应用程序组件输入框中的文本时,该按钮被正确禁用。

【问题讨论】:

  • 你的地址组件是什么样的?是 ControlValueAccessor 吗?
  • 只是一个简单的组件,类体中没有任何内容
  • 尝试将其添加到模板的底部:{{ form1.value | json }} 并查看是否包含两个输入元素或仅一个。我知道不能将表单拆分为随路由器加载的组件。它也可能无法在嵌套组件中“找到”项目。
  • @DeborahK 是的,您是来自地址组件的正确元素,但未在 json 中显示。有什么办法可以实现吗?
  • 虽然 AJT82 的回答很棒而且很有帮助,但它是一种采用其他方法的解决方案,而不是要求的。我提出了一个解决方案如何在多个组件上实现表单here:(进一步向下)。

标签: angular typescript angular-forms


【解决方案1】:

根据我的经验,这种表单域组合很难用模板驱动的表单来制作。地址组件中嵌入的字段不会在表单中注册(NgForm.controls 对象),因此在验证表单时不会考虑它们。

  • 您可以创建一个包含所有验证的 ControlValueAccessor 组件(接受 ngModel 属性),但是很难显示验证错误和传播更改(地址被视为具有复杂值的单个表单字段)。
  • 您可能可以将表单引用传递到 Address 组件并在其中注册您的内部控件,但我没有尝试过,这似乎是一种奇怪的方法(我在任何地方都没有见过)。
  • 您可以切换到反应式表单(而不是模板驱动),将表单组对象(表示地址)传递到地址组件,在表单定义中保留验证。你可以在这里看到一个例子https://scotch.io/tutorials/how-to-build-nested-model-driven-forms-in-angular-2

【讨论】:

  • 谢谢 Jan。在发布此问题之前,我查看了该链接。但是我无法清楚地理解发生了什么,因为它与 *ngFor 循环结合使用。这个还有其他简单的例子吗?也许是没有循环的同一个例子。
  • 我发现的所有示例都使用数组和 *ngFor,因为它可能是重用表单部件的最常见用例。但我认为这不会影响解决方案 - 只是不要将要进入 Address 组件的表单组编入索引并跳过 formArray 部分。
【解决方案2】:

我会使用一种效果很好的反应形式,至于您的评论:

还有其他简单的例子吗?也许是没有循环的同一个例子

我可以举个例子。您需要做的就是嵌套 FormGroup 并将其传递给孩子。

假设您的表单看起来像这样,并且您想将address formgroup 传递给孩子:

ngOnInit() {
  this.myForm = this.fb.group({
    name: [''],
    address: this.fb.group({ // create nested formgroup to pass to child
      street: [''],
      zip: ['']
    })
  })
}

然后在你的父级中,只需传递嵌套的表单组:

<address [address]="myForm.get('address')"></address>

在您的孩子中,将@Input 用于嵌套表单组:

@Input() address: FormGroup;

在你的模板中使用[formGroup]:

<div [formGroup]="address">
  <input formControlName="street">
  <input formControlName="zip">
</div>

如果您不想创建实际的嵌套表单组,则不需要这样做,您可以将父表单传递给子表单,因此如果您的表单如下所示:

this.myForm = this.fb.group({
  name: [''],
  street: [''],
  zip: ['']
})

你可以通过任何你想要的控制。使用与上面相同的示例,我们只想显示streetzip,子组件保持不变,但模板中的子标签将如下所示:

<address [address]="myForm"></address>

这是一个

Demo 的第一个选项,这是第二个 Demo

更多关于嵌套模型驱动表单的信息here

【讨论】:

  • 此方法将要求您从父级中的子级复制相同的 FormGroup 定义。在上面的示例中,父级声明 address 带有字段 street n zip 但是,地址组件也具有相同的 FormGroup 定义(即 street 和zip) 除非我误解了您的解决方案。如果嵌套层次多且表单复杂,那么这种重复是子代码到父代码变得混乱。避免它的方法是将父表单传递给子组件,我不是这方面的专家:(仍在学习。
  • 如何在不为地址创建 formGroup 的情况下拆分组件
  • @RenilBabu 可以将整个表单传递给子组件。
  • 它不工作...抛出错误来创建一个 formGroup 并在孩子中使用它
  • @RenilBabu,它应该可以工作,你能为此创建一个演示,我很乐意看看:)
【解决方案3】:

我想分享在我的案例中完成这项工作的方法。我创建了以下指令:

import { Directive } from '@angular/core';
import { ControlContainer, NgForm } from '@angular/forms';

@Directive({
  selector: '[appUseParentForm]',
  providers: [
    {
      provide: ControlContainer,
      useFactory: function (form: NgForm) {
        return form;
      },
      deps: [NgForm]
    }
  ]
})
export class UseParentFormDirective {
}

现在,如果你在子组件上使用这个指令,例如:

<address app-use-parent-form></address>

来自 AddressComponent 的控件将被添加到 form1。因此,表单的有效性还取决于子组件内控件的状态。

仅使用 Angular 6 进行检查

【讨论】:

    【解决方案4】:

    在模板驱动的表单中也有一种方法可以做到这一点。 ngModel 会在每个组件上自动创建一个单独的表单,但是您可以通过将其添加到您的组件来注入父组件的表单:

    @Component({
    viewProviders: [{ provide: ControlContainer, useExisting: NgForm}]
    }) export class ChildComponent
    

    您必须确保每个输入都有一个唯一的名称。所以如果你使用 *ngFor 来调用你的子组件,你必须将索引(或任何其他唯一标识符)放入 name ,例如:

    [name]="'address_' + i"
    

    如果你想将你的表单组织成 FormGroups,你可以使用 ngModelGroup 和

    viewProviders: [{ provide: ControlContainer, useExisting: NgModelGroup }]
    

    而不是 ngForm 并添加 [ngModelGroup]="yourNameHere" 到您的一些包含标签的子组件 html。

    【讨论】:

    • 这可行,但我不知道如何在使用 viewProvider 时设置测试。您能展示一个测试示例吗?
    • 我还没有使用viewProviders的测试,所以我恐怕帮不了你。但是,如果您知道这一点,如果您分享它会很棒!
    【解决方案5】:

    使用最新版本的 Angular 2+,您可以将表单拆分为多个组件,而无需将 formGroup 实例作为输入传递给子组件。

    您可以通过将此提供程序导入您的子组件来实现此目的:

    viewProviders: [{provide: ControlContainer,useExisting: FormGroupDirective}]
    

    然后您也可以通过注入 formGroupDirective 来访问主表单:

    constructor(private readonly formGroupDirective: FormGroupDirective)
    

    【讨论】:

      猜你喜欢
      • 2020-08-21
      • 2011-07-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多