如何将动态创建的输入元素绑定到FormGroup?

时间:2019-10-25 12:52:52

标签: angular forms angular-reactive-forms

我有一个分层的对象,它描述了一个不太简单的布局,其中包含输入字段等,就像这样:

{
  "type": "HorizontalLayout",
  "margin": false,
  "children": [
    {
      "type": "TextField",
      "id": "code",
      "caption": "Code of product",
      "width": "100px"
    },
    {
      "type": "NumberField",
      "id": "amount",
      "caption": "Amount of pieces"
    }
  ]
}

此对象的深度不是固定的,可以包含许多嵌套容器和输入字段。将对象呈现为具有所有所需元素的DOM片段并将其附加到所需元素不是很困难,但是我如何才能将呈现的输入绑定到现有的FormGroup?

更新:更具体地说-如何将用document.createElement创建的输入元素绑定到现有的FormGroup

3 个答案:

答案 0 :(得分:0)

在您的类中创建一个FormGroup,然后递归遍历children数组中的每个项目,然后向该表单添加一个新控件。

Here是有关如何在FormGroup中使所有控件位于同一级别的快速示例。 HTML将显示RawValues表单,因此您可以看到HTML表单。

在此示例中,'data'属性是固定的,但是您可以修改此属性并将其他一些数据传递到setupForm方法中。

import { Component, OnInit} from '@angular/core';
import {FormBuilder, FormGroup, FormControl, Validators} from '@angular/forms';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
  name = 'Angular';
  form: FormGroup;
  data: {} = {
    "type": "HorizontalLayout",
    "margin": false,
    "children": [
      {
        "type": "TextField",
        "id": "code",
        "caption": "Code of product",
        "width": "100px"
      },
      {
        "type": "NumberField",
        "id": "amount",
        "caption": "Amount of pieces",
      }
    ]
  };

  constructor(private fb: FormBuilder){}

  ngOnInit(){
    this.form = this.fb.group({});
    // pass the data form which form controls should be created
    this.setupForm(this.data);
  }

  setupForm(field: any){
    if(field.children && field.children.length){
        const children = field.children;
        // Loop through each item in the children array and add a form control
        for(const item of children){
          // This assumes that all your fields have unique Ids
          this.form.addControl(item.id, new FormControl('test', [Validators.required]))
          // Process nested fields 
          this.setupForm(item);
        }
    }   
  }
}

您提供的样本数据具有顶级对象。我怀疑您可能有多个“ Horizo​​ntalLayout”。如果是这种情况,您可以修改setupForm以处理数组而不是对象。

setupForm还将查找“ children”属性,并递归调用setupForm。这使您可以进行多层嵌套。

希望这会有所帮助。

答案 1 :(得分:0)

constructor(private formBuilder: FormBuilder) { }

dynamicForm: FormGroup;

ngOnInit() {
    // set the fieldNames variable to generate form
    const fieldNames = ['field1', 'field2', 'field3'];

    this.dynamicForm = this.generateForm(fieldNames );
}

generateForm(fieldNames: string[]): FormGroup { // this is your form group
    const formFields: any = { };

    fieldNames.forEach(name=> {
        formFields[name] = [null, [Validators.required]];
    });

    return this.formBuilder.group(formFields);
 }

答案 2 :(得分:0)

最后,我最终在组件的模板中创建了输入元素。该组件是为布局对象的每个节点递归创建的(如article of Ben Nadel中所述(谁值得为此多谢))。 FormGroup的一个实例被显式传递到所有NodeComponent / NodeTreeComponent,这使得将输入元素绑定到它很容易。

描述布局的对象示例:

{
  "type": "Container",
  "id": "filter",
  "children": [
    {
      "type": "HorizontalLayout",
      "spacing": true,
      "children": [
        {
          "type": "TextField",
          "id": "code",
          "caption": "Код",
          "width": "100px"
        },
        {
          "type": "TextField",
          "id": "label",
          "caption": "Наименование",
          "width": "200px"
        },
        {
          "type": "TextField",
          "id": "days",
          "caption": "Дней",
          "width": "100px"
        },
        {
          "type": "TextField",
          "id": "months",
          "caption": "Месяцев",
          "width": "100px"
        },
        {
          "type": "Check3Box",
          "id": "isActive",
          "caption": "Активен",
          "height": "20px",
          "alignment": "bottom_left"
        },
        {
          "type": "Check3Box",
          "id": "isAbsDeleted",
          "caption": "Удален в АБС",
          "height": "20px",
          "alignment": "bottom_left"
        }
      ]
    }
  ]
}

main.component.html的一部分

<ng-template [ngIf]="filterForm">
    <form [formGroup]="my_awesome_form">
        <my-tree [rootNode]="elements" [form]="my_awesome_form"></my-tree>
    </form>
</ng-template>

main.component.ts的一部分

// form creation
public my_awesome_form = new FormGroup({});

// saving a layout object to a public variable
public elements = some_data_service.getLayout();

// getting input fields from the layout object
const inputs = Utils.deepFind(this.elements, 'caption', '');

// привязываем поля к форме
inputs.forEach((input) => {
    this.filterForm.addControl(input.id, new FormControl(null));
});

TreeComponent

import {Component} from "@angular/core";
import {LayoutElement} from "@toolkit/models/layout.model";
import {FormGroup} from "@angular/forms";

@Component({
  selector: "my-tree",
  inputs: [
    "rootNode",
    "form"
  ],
  outputs: [],
  template: `<my-tree-node [node]="rootNode" [form]="form"></my-tree-node>`
})
export class TreeComponent {
  public rootNode: LayoutElement | null;
  public form: FormGroup;

  constructor() {
    this.rootNode = null;
  }
}

TreeNodeComponent

import {Component} from "@angular/core";
import {LayoutElement} from "@toolkit/models/layout.model";
import {FormGroup} from "@angular/forms";

@Component({
  selector: "my-tree-node",
  inputs: [
    "node",
    "form"
  ],
  outputs: [],
  template:
      `
      <ng-template [ngIf]="node && node.type">
          <span [ngSwitch]="node.type" [formGroup]="form">
              <!-- пример создания контейнера -->
              <div *ngSwitchCase="'Container'">
                  <div *ngIf="node.children">
                      <ng-template ngFor let-child [ngForOf]="node.children">
                          <my-tree-node [node]="child" [form]="form"></my-tree-node>
                      </ng-template>
                  </div>
              </div>
              <!-- пример создания поля ввода -->
              <ng-template [ngSwitchCase]="'TextField'">
                  <input type="text" [attr.placeholder]="node.caption" [formControlName]="node.id"/>
              </ng-template>
              <!-- обработка не зарегистрированного типа -->
              <div *ngSwitchDefault>
                  <div class="p-3 mb-2 bg-danger text-white">Element of type "{{ node.type }}" can't be processed!</div>
              </div>
          </span>
      </ng-template>
    `
})
export class TreeNodeComponent {
  public node: LayoutElement | null;
  public form: FormGroup;

  constructor() {
    this.node = null;
  }
}

感谢所有尝试提供帮助的人!顺便说一句,对于这么大的代码块,我没找到如何插入扰流器