Как связать динамически созданный элемент ввода с FormGroup? - PullRequest
0 голосов
/ 25 октября 2019

У меня есть иерархический объект, который описывает не очень простой макет с полями ввода и т. Д., Что-то вроде этого:

{
  "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 голосов
/ 26 октября 2019
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);
 }
0 голосов
/ 30 октября 2019

Наконец я закончил тем, что создал элементы ввода в шаблоне компонента. Этот компонент рекурсивно создается для каждого узла объекта макета (как описано в статье Бена Наделя (который заслуживает за это большое виртуальное спасибо)). Экземпляр 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;
  }
}

Спасибо всем, кто пытался помочь! И кстати извините за такие большие куски кода, я не нашел, как вставить спойлеры

0 голосов
/ 26 октября 2019

Создайте FormGroup в вашем классе и рекурсивно выполните цикл по каждому элементу в массиве children, затем добавьте новый элемент управления в форму.

Здесь - это быстрый пример того, как это может бытьсделано, если вы хотите, чтобы все элементы управления находились на одном уровне в 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);
        }
    }   
  }
}

Предоставленные вами образцы данных имеют верхний уровень как объект. Я подозреваю, что у вас может быть несколько «HorizontalLayout». в этом случае вы можете изменить setupForm для обработки массива вместо объекта.

setupForm также будет искать свойство 'children' и рекурсивно вызывать setupForm. это позволяет вам иметь несколько уровней вложенности.

Надеюсь, это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...