#angular #typescript #angular-reactive-forms #formarray #form-control
#angular #typescript #angular-реактивные формы #formarray #управление формой
Вопрос:
Я реализовал stackblitz, в котором вы можете создать динамическую форму, используя некоторую конфигурацию. Все работает нормально, пока вы не используете FormArray
. В чем идея? В принципе, в вашем файле конфигурации может быть много полей разного типа. Например checkbox
, text
, и т.д. number
select
В каком-то случае у вас должна быть более сложная структура, и поэтому у вас должен быть FormArray
. В этом стекблите я попытался представить, что происходит
Итак, когда я нашел 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:
Что ж, хорошая работа, но вам нужно изменить несколько вещей, чтобы достичь того, чего вы хотите.
- Вам нужно изменить способ инициализации массива формы. У вас есть именованные элементы управления в вашем
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
- Вам нужно изменить
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