Angular 2 使用嵌套组件创建反应式表单

Posted

技术标签:

【中文标题】Angular 2 使用嵌套组件创建反应式表单【英文标题】:Angular 2 creating reactive forms with nested components 【发布时间】:2017-07-20 18:46:54 【问题描述】:

我的要求是我需要创建一个带有嵌套组件的表单。 我正在为每个表单字段创建组件,这意味着文本框将 是一个组件,对于单选按钮会有另一个组件。<form [formGroup]="myForm"> <textbox-component></textbox-component> <radioButton-component></radioButton-component> </form>

我想使用响应式表单来创建我想要的表单 html 保持不变,并且仅通过 typescript 进行表单验证。

但我找不到任何解决方案,我们如何才能嵌套反应式表单 组件。

【问题讨论】:

也许每个组件都会保留自己的验证器。在提交时,每个组件通过服务或事件发射器向父组件发出值...未测试,但这应该可以工作 我无法在子组件内的 html 中添加 formControlName 属性(反应式表单所必需的),它抛出父指令 fromGroup 不存在的错误(因为它存在于父) 这就是为什么每个组件都应该保留自己的 formControlName 和验证器的原因。父组件角色将实例化表单并在提交时获取所有数据。子组件将触发有效或错误事件并通知父组件是否允许提交...看看Nested Model Driven Form 我看过这篇文章,但它是一个非常静态的例子。我通过动态渲染组件来生成表单。搜索了网络,但除了这篇文章之外没有找到任何帮助。我认为 Angular 2 处于非常初始阶段,也没有适当的文档。那么如何为 Angular 2 中的新功能寻求帮助呢? 【参考方案1】:

在我的研究和实验之后,我找到了我的问题的一个答案,所以我自己来回答。如果它节省了某人的时间,那么我会很高兴。

如果您想使用嵌套组件创建反应式表单,那么您可以执行以下操作

在这里,我正在创建一个包含两个嵌套组件的表单,一个用于文本框,另一个用于单选按钮

你的父组件可以是这样的

<form [formGroup]="myForm">
    <child-textbox-component [parentFormGroup]="myForm">
    </child-textbox-component>
    <child-radio-button-component [parentFormGroup]="myForm">
    </child-radio-button-component>
</form>

我们将 FormGroup 对象作为输入传递给在父组件中创建的子组件 作为子组件的输入,他们将在他们的子组件中使用这个 FormGroup 对象 组件来设计类的特定控件

你的子组件会是这样的

子文本框组件

<div class="form-group" [formGroup]="parentFormGroup">
  <label>
    control.caption
  </label>
  <input class="form-control" type="text" [title]="control.toolTip" 
    [attr.maxlength]="control.width" [name]="control.name"
    [value]="control.defaultValue" [formControlName]="control.name"/>
</div>

子单选按钮组件

<div class="form-group" [formGroup]="parentFormGroup">
  <label>
    control.caption
  </label>
  <div>
      <label *ngFor="let value of control.values; let idx = index"
        class="radio-inline" [title]="control.tooltip">
        <input type="radio" [name]="control.name" [formControlName]="control.name"/>
         value 
      </label>
  </div>
</div>

这里的控制是模型类保存要显示的数据 子组件。

这样您就可以使用嵌套组件生成表单, 这样您就不必将表格(可以说是大表格)单独保存 零件。您可以将其分解为尽可能多的子组件和表单 也可以使用 Angular 2 的反应形式轻松创建和维护。您也可以轻松添加验证。

在回答这个问题之前我点击了这些链接

    something similar on ***

    angular 2 dynamic forms

【讨论】:

您如何在子组件中进行验证?我们可以在子组件中添加验证或在父组件中定义所有验证吗?您可以发布您的 .ts 文件代码 这个答案看起来很有用,但并不完整。你能把你的TS代码贴出来吗?即,设置 FormGroup 对象及其控件的方式和位置。 你可以按照这个教程这个很棒的toddmotto.com/… 我根据这个解决方案做了一个简单的例子:stackblitz.com/edit/angular-1drscm(基于coryrylan.com/blog/…的switch示例)。 只有我的两分钱。我刚刚使用这种方法处理了一个现有的 Angular 项目,您将表单作为@Input/@Output 传递,维护它真的很痛苦。【参考方案2】:

对 Mhesh 的回答的附加说明,您可以构建这个相同的解决方案,而无需在 HTML 中注入 [parentFormGroup]。您可以通过在可重用表单组上关注此 Stack Overflow post 来做到这一点。

这真是太好了。

示例

要采用现有的解决方案,你可以做同样的事情,除了:

你的父组件可以这样,不需要传入任何额外的参数

<form [formGroup]="myForm">
    <child-textbox-component></child-textbox-component>
    <child-radio-button-component></child-radio-button-component>
</form>

另外请注意,您可以像这样设置表单组:

<form [formGroup]="myForm">
    <child-textbox-component></child-textbox-component>
    <child-radio-button-component formGroupName="myGroup"></child-radio-button-component>
</form>

子文本框组件

<div class="form-group" [formGroup]="controlContainer.control">
  <label>
    control.caption
  </label>
  <input class="form-control" type="text" [title]="control.toolTip" 
    [attr.maxlength]="control.width" [name]="control.name"
    [value]="control.defaultValue" [formControlName]="control.name"/>
</div>

要启用此功能,您需要将 ControlContainer 注入您的 @Component

@Component(
    moduleId: `MODULE_ID_HERE`,
    selector: "child-textbox-component",
    templateUrl: "childTextbox.component.html",
)
export class ChildTextboxComponent 
    constructor(private controlContainer: ControlContainer, OTHER_PARAMETERS) 
    

【讨论】:

【参考方案3】:

为了扩展可能的答案列表,this article by Alexey Zuev 建议在组件装饰器中使用provide:ControlContaineruseExisting:NgForm 作为将ngForm 指令传递给子组件的一种方式。

@Component(
  selector: 'registrant',
  templateUrl: 'app/register/registrant.component.html',
  **viewProviders: [  provide: ControlContainer, useExisting: NgForm  ]**
)

【讨论】:

我们如何动态验证子组件?【参考方案4】:

尝试使用嵌套日期选择器时出现同样的问题。上面的解决方案对我不起作用。我通过发出更改日期并直接访问表单控制解决了这个问题。

日历输入.component.ts

@Component(
  selector: 'app-calendar-input',
  templateUrl: '...',
)
export class CalendarInputComponent 
  @Output() dateChanged = new EventEmitter<Date|null>();  
  onClose() 
    this.dateChanged.emit(this.currentDate);
  
  ...

模板

    <app-calendar-input
      ...
      (dateChanged)="setFormControlValueByItem(item, col.propName, $event)">
    </app-calendar-input>

以及带有表单组和表单控件的组件:

  setFormControlValueByItem(item: FormItem<I, FormGroup>, propName: string, value: any): void 
    item.frameworkFormGroup.controls[propName].setValue(value);
    item.frameworkFormGroup.controls[propName].markAsTouched();
    item.frameworkFormGroup.controls[propName].markAsDirty();
  

item.frameworkFormGroup 在我的例子中是 FormGroup 的实例,propName 是我通常会写入属性formControlName 的字段名称。

此解决方案的一个缺点是,在某些情况下,它会降低代码对 FormGroup 和 FormConrol API 中的破坏通道的抵抗力。

另见:https://angular.io/api/forms/FormGroup 和https://angular.io/api/forms/FormControl

【讨论】:

【参考方案5】:

只需通过 [formGroup] 绑定在表单块中传递相同或子表单组。

【讨论】:

以上是关于Angular 2 使用嵌套组件创建反应式表单的主要内容,如果未能解决你的问题,请参考以下文章

Angular4 Reactive嵌套表单问题

如何将表单组绑定到 Angular 中嵌套的动态创建的组件

Angular 9“没有提供ControlContainer的提供者” VSCode错误,带有嵌套的Form Builder表单组

带有子组件和验证的 Angular 2 嵌套表单

Angular 在父组件嵌套表单中使用子组件表单

使用 Angular 元素调用组件构造函数两次