通过验证将表单划分为多个组件

我想用角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>

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

61349 次浏览

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

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

我会使用一个反应的形式,它工作得很好,至于你的评论:

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

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

假设您的表单如下所示,并且您想将 address表单组传递给子表单组:

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>

这里有一个

演示 第一个选项,这里是第二个 < strong > < a href = “ https://stackblitz.com/edit/angle-hzq5vy? file = src/app/app.Component ent.html”rel = “ noReferrer”> 演示

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

我想分享一下在我的案例中起作用的方法。我创建了以下指令:

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。因此,表单的有效性也将取决于子组件中控件的状态。

只用角度6检查过

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

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

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

[name]="'address_' + i"

如果希望将窗体结构化为“窗体组”,可以使用 ngModelGroup 和

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

而不是 ngForm 和 add [ngModelGroup]="yourNameHere" 包含标记的 html 子组件。

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

可以通过将此提供程序导入到子组件中来实现这一点:

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

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

constructor(private readonly formGroupDirective: FormGroupDirective)

大多数反对的评论都是样板文件: 我见过使用这种方法的项目,管理起来简直是一场灾难。

我要感谢用户@elia 的回答。

为了方便起见,我只能补充一点,我们可以将提供者设置为常量:

import { Provider } from '@angular/core';
import { ControlContainer, FormGroupDirective } from '@angular/forms';


export const FormPart: Provider = {
provide: ControlContainer,
useExisting: FormGroupDirective,
};

并将其导入到所需的组件中,如:

@Component({
...
viewProviders: [FormPart],
})

并使用上述构造函数代码创建指令,在需要时提供父表单,从而将表单业务逻辑分离成较小的块。

我曾经尝试探索装饰器是如何在 Angular 中使用 viewProvider 来扩展@Component 的,但是这是一个更复杂的主题,似乎是不可能的,所以我放弃了。会很高兴听到其他人在这方面的经验。我认为这是一个很好的特点,角度支持和使工作与复杂的形式更容易。