FormArray в динамической форме angular

#angular #typescript #angular-reactive-forms #formarray #form-control

#angular #typescript #angular-реактивные формы #formarray #управление формой

Вопрос:

Я реализовал stackblitz, в котором вы можете создать динамическую форму, используя некоторую конфигурацию. Все работает нормально, пока вы не используете FormArray . В чем идея? В принципе, в вашем файле конфигурации может быть много полей разного типа. Например checkbox , text , и т.д. number select В каком-то случае у вас должна быть более сложная структура, и поэтому у вас должен быть FormArray . В этом стекблите я попытался представить, что происходит

https://stackblitz.com/edit/angular-dynamic-form-builder-gycshd ?file=app/dynamic-form-builder/dynamic-form-builder.component.ts

Итак, когда я нашел array тип, я собираюсь создать FormArray вместо простого FormControl это мой список полей

  public fields: any[] = [
    {
      type: "text",
      name: "firstName",
      label: "First Name",
      required: true
    },
    {
      type: "text",
      name: "lastName",
      label: "Last Name",
      required: true
    },
    {
      type: "text",
      name: "email",
      label: "Email",
      required: true
    },
    {
      type: "text",
      name: "description",
      label: "Description",
      required: true,
      multiLine: true
    },
    {
      type: "dropdown",
      name: "country",
      label: "Country",
      value: "in",
      required: true,
      options: [{ value: "in", label: "India" }, { value: "us", label: "USA" }]
    },
    {
      type: "array",
      name: "users",
      label: "Users",
      structure: "users.json"
    }
  ];
  

И вот мой компонент для создания динамических форм

 export class DynamicFormBuilderComponent implements OnInit {
  @Output() onSubmit = new EventEmitter();
  @Input() fields: any[] = [];
  form: FormGroup;
  allDatas: any;
  constructor() {}

  ngOnInit() {
    let fieldsCtrls = {};
    this.allDatas = getData();
    for (let f of this.fields) {
      if (f.type != "array") {
        fieldsCtrls[f.name] = new FormControl(
          this.allDatas[f.name] || "",
          Validators.required
        );
      } else {
        fieldsCtrls[f.name] = new FormArray([
          new FormControl(this.allDatas[f.name] || "", Validators.required)
        ]);
      }
    }
    this.form = new FormGroup(fieldsCtrls);
  }
}
  

Как вы можете видеть, я устанавливаю значение из allDatas, если оно существует, и создаю FormGroup с помощью FormControls и FormArray. Но когда я генерирую arraybox , у меня возникают ошибки. Это компонент arraybox, который отображает элемент управления FormArray:

 import { HttpClient } from "@angular/common/http";
import { Component, Input, OnInit } from "@angular/core";
import { FormGroup } from "@angular/forms";
import { ConfigService } from "../../config.service";

@Component({
  selector: "arraybox",
  template: `
    <div [formGroup]="form">
      <div formArrayName="{{ field.name }}">
        <div
          *ngFor="let obj of arrayFileds; index as idx"
          [formGroupName]="idx"
        >
          <input
            attr.type="{{ obj.type }}"
            class="form-control"
            placeholder="{{ obj.name }}"
            id="{{ obj.name }}"
            name="{{ obj.name }}"
            formControlName="{{ idx }}"
          />
        </div>
      </div>
    </div>
  `
})
export class ArrayBoxComponent implements OnInit {
  @Input() field: any = {};
  @Input() form: FormGroup;
  arrayFileds: any = [];

  get isValid() {
    return this.form.controls[this.field.name].valid;
  }
  get isDirty() {
    return this.form.controls[this.field.name].dirty;
  }

  constructor(private config: ConfigService) {}

  ngOnInit(): void {
    this.arrayFileds = this.config.getData(this.field.structure);
    console.log(this.arrayFileds);
  }
}
  

getData() возвращает структуру этого FormArray. В этом случае

 [{
    "label": "Username",
    "name": "userName",
    "type": "text"
}, {
    "label": "User email",
    "name": "userEmail",
    "type": "text"
}]
  

Ошибки в консоли:

 ERROR
Error: Cannot find control with path: 'users -> 0 -> 0'
ERROR
Error: Cannot find control with path: 'users -> 1'
  

Я действительно не знаю, что здесь не так. Как я могу использовать FormArray для отображения полей с их значениями в этой форме?

ПРИМЕЧАНИЕ. stackblitz выдает мне ошибку, если я использую http для анализа json, поэтому сейчас я использую прямой импорт json. В принципе, this.field.structure в этом примере не используется (но должно быть).

Ответ №1:

Что ж, хорошая работа, но вам нужно изменить несколько вещей, чтобы достичь того, чего вы хотите.

  1. Вам нужно изменить способ инициализации массива формы. У вас есть именованные элементы управления в вашем FormArray , поэтому вам нужно вставить FormGroup в свой FormArray :
  for (let f of this.fields) {
      if (f.type != "array") {
        fieldsCtrls[f.name] = new FormControl(
          this.allDatas[f.name] || "",
          Validators.required
        );
      } else { // changed part
        fieldsCtrls[f.name] = new FormArray([
          ...this.allDatas[f.name].map(
            r =>
              new FormGroup(
                Object.entries(r).reduce((acc, [k, v]) => {
                  acc[k] = new FormControl(v || "", Validators.required);
                  return acc;
                }, {})
              )
          )
        ]);
      }
  

Приведенный выше код в основном генерирует FormGroup , который имеет ключи объекта как FormControls

  1. Вам нужно изменить ArrayBox шаблон ваших компонентов, как показано ниже :
  <input
            attr.type="{{ obj.type }}"
            class="form-control"
            placeholder="{{ obj.name }}"
            id="{{ obj.name }}"
            name="{{ obj.name }}"
            formControlName="{{ obj.name }}"
          />
  

У вас было formControlName="{{ idx }}" , что заставит Angular искать FormControl имя 0 1 , которое является одной из причин, по которой вы получаете Error: Cannot find control with path: 'users -> 0 -> 0' ошибку. Другая причина в том, что вы добавляли непосредственно FormControl s вместо FormGroup s. Как ArrayBox указано в вашем шаблоне FormArray , имеет массив FormGroup

Рабочий стекблитц

Как @End.Игра заметила, что часть массива все еще нуждается в исправлении. Основная причина проблемы заключается в ArrayBox шаблоне. Вы перебирали свои объекты конфигурации для отображения своей формы. Но поскольку это a FormArray , вам также необходимо повторить этот шаблон для подсчета FormArray . Поэтому *ngFor для достижения этой цели вам понадобится другой :

    <div [formGroup]="form">
      <div formArrayName="{{ field.name }}">
        <div *ngFor="let frm of formArray.controls; index as idx">
          <div formGroupName="{{ idx }}">
            <div *ngFor="let obj of arrayFileds;">
              <input
                attr.type="{{ obj.type }}"
                class="form-control"
                placeholder="{{ obj.name }}"
                id="{{ obj.name }}"
                name="{{ obj.name }}"
                formControlName="{{ obj.name }}"
              />
            </div>
            <hr />
          </div>
        </div>
      </div>
    </div>
  

Также обратите внимание, что idx переменная перемещена в родительский цикл.

Комментарии:

1. Спасибо за ваш ответ. Ваш пример частично работает. Я имею в виду, все в порядке, и все идет так, как я хочу, но он показывает только один результат. Я вижу только {userName: «Второй тест», userEmail: «secondEmail@test.com » } и не в первый раз. Можете ли вы это подтвердить?

2. @End. Игра, которую я заметил сейчас. Обновит вопрос и stackblitz.

Ответ №2:

Проверьте этот stackblitz пример:-

https://stackblitz.com/edit/angular-dynamic-form-creation

надеюсь, это будет полезно для вас!

Комментарии:

1. Это не совсем то, что я искал. В этом примере создается только FormControl . Не FormArray