puff, это большое и сложное улучшение кода, позволяющее использовать fromGroups и formArray, но мы собираемся попробовать.
Сначала мы собираемся изменить несколько компонентов DynamicFormBuilderComponent.
Мы собираемся создать функцию getForm, чтобы можно было рекурсивно создавать formGroup.
getForm(group: FormGroup, fields: any[]) {
for (let f of fields) {
switch (f.type) {
case "group":
group.addControl(f.name, new FormGroup({}));
this.getForm(group.get(f.name) as FormGroup, f.children);
break;
case "checkbox":
group.addControl(f.name, new FormGroup({}));
const groupOption = group.get(f.name) as FormGroup;
for (let opt of f.options) {
groupOption.addControl(opt.key, new FormControl(opt.value));
}
break;
case "array":
group.addControl(f.name, new FormArray([]));
const array = group.get(f.name) as FormArray;
if (f.value)
{
f.value.forEach(x=>array.push(this.addGroupArray(f.children)))
array.patchValue(f.value)
}
break;
default:
group.addControl(
f.name,
new FormControl(f.value || "", Validators.required)
);
break;
}
}
Обратите внимание на это, прежде чем мы создадим объект fields
и, наконец, добавим поля объекта в formGroup. Используя эту функцию, мы напрямую создаем пустую группу форм и добавляем элементы управления формами, или группу форм, или массив форм, используя group.addControl
. Это позволяет нам вызывать рекурсивную функцию, если это необходимо.
Итак, в ngOnInit делаем
ngOnInit() {
this.getForm(this.form, this.fields);
}
Посмотрите, что мы можем получить доступ к форме из родителя, используя переменную шаблона.
<dynamic-form-builder #dynamic ..>
{{dynamic.form?.value|json}}
И как я решаю, что форма имеет точную модель, прежде чем у нас будет объект с {fields:exactModel}-
И нам нужна вспомогательная функция для создания formGroup массива форм.
addGroupArray(fields:any[])
{
const group:FormGroup=new FormGroup({})
fields.forEach(x=>{
group.addControl(x.name,new FormControl(null,x.required?Validators.required:null))
})
return group
}
Наш FieldBuilderComponent должен учитывать два новых типа полей: массив и группа. Я собираюсь поместить его в набор полей
<!--in case group we repeat the field-builder using
as form the "getFormGroup" and as field "field.children"
-->
<fieldset *ngSwitchCase="'group'">
<legend>{{field.name}}</legend>
<field-builder *ngFor="let item of field.children"
[form]="getFormGroup(field.name)" [field]="item">
</field-builder>
</fieldset>
<!--in case array we create a table and use the function
"getFormArray"
-->
<fieldset *ngSwitchCase="'array'">
<legend>{{field.name}}</legend>
<table [formArrayName]="field.name">
<tr>
<th *ngFor="let item of field.children">{{item.label}}</th>
<th>
<button class="btn btn-primary" (click)="getFormArray(field.name).push(this.addGroupArray(field.children))">Add</button>
</th>
</tr>
<tr *ngFor="let group of getFormArray(field.name).controls;let i=index" [formGroupName]="i">
<td *ngFor="let item of field.children">
<field-builder noLabel="true" [form]="getFormArray(field.name).at(i)"
[field]="item">
</field-builder>
</td>
<td><button class="btn btn-primary" (click)="getFormArray(field.name).removeAt(i)">Delete</button></td>
</tr>
</table>
</fieldset>
Я использую две вспомогательные функции, которые возвращают только formGroup и formArray
getFormGroup(field:string)
{
return this.form.get(field) as FormGroup
}
getFormArray(field:string)
{
return this.form.get(field) as FormArray
}
Посмотрите, как внутри мы вызываем собственный компонент. Мне нужно добавить атрибут noLabel, чтобы не отображать метку, если мы управляем FormArrayLabel
//in constructor
constructor(@Attribute('noLabel') noLabel) {
this.noLabel=noLabel || false;
}
И использовать
<label *ngIf="!noLabel" ....></label>
Мне нужно еще раз повторить функцию addGroupArray
в этом компоненте (другого способа я не представляю)
Ну, единственное, что нужно учитывать, это то, как погрузить значение в formArray, увидеть, что поле массива похоже на:
{
type: "array",
name: "arrayName",
value:[{firstName:"array",lastName:"array lastName"}],
children: [
{
type: "text",
name: "firstName",
label: "First Name",
required: true
},
{
type: "text",
name: "lastName",
label: "Last Name",
required: true
}
]
},
Как обычно, это stackblitz без гарантии
ПРИМЕЧАНИЕ. атом для параметров должен быть
<div [formGroup]="form">
<div class="form-check" *ngFor="let opt of field.options">
<label class="form-check-label">
<input [formControlName]="field.name" class="form-check-input" type="radio" [value]="opt.key" >
{{opt.label}}
</label>
</div>
</div>
Обновить На самом деле мне никогда не нравилось разбивать серию флажков в массиве со значениями true/false. Более естественно, что значение было, например. c,f и установлен флажок кулинария и рыбалка.
Ну, во-первых, измените флажок атома, используя [ngModel]
и (ngModelChange)
. Сначала мы собираемся создать две вспомогательные функции:
//a simple "getter" to get the value
get value() {
return this.form ? this.form.get(this.field.name).value : null;
}
//we pass "checked" and the "key" of the option
change(checked: boolean, key: any) {
const oldvalue = this.form.get(this.field.name).value || null;
//if has no value
if (!oldvalue) {
//use setValue with the "key" (if checked) or null
this.form.get(this.field.name).setValue(checked ? "" + key : null);
return;
} else {
//in value store all the options that fullfilled with the condition
const value = checked
? this.field.options.filter( //is in the old value or is the key
x => oldvalue.indexOf(x.key) >= 0 || x.key == key
)
: this.field.options.filter( //is in the old value and is not the key
x => oldvalue.indexOf(x.key) >= 0 && x.key != key
);
//we give the value null if there're no options that fullfilled
//or a join of the keys
this.form
.get(this.field.name)
.setValue(value.length > 0 ? value.map(x => x.key).join(",") : null);
}
}
Что ж, теперь мы можем использовать наши [ngModel] и (ngModelChange). Нам нужно сказать Angular, что это автономный
<div [formGroup]="form">
<div *ngFor="let opt of field.options" class="form-check form-check">
<label class="form-check-label">
<input
[ngModel]="value && value.indexOf(opt.key) >= 0"
(ngModelChange)="change($event, opt.key)"
[ngModelOptions]="{ standalone: true }"
class="form-check-input"
type="checkbox"
id="inlineCheckbox1"
value="option1"
/>
{{ opt.label }}</label>
</div>
</div>
Итак, убираем случай чекбокса в out функции getForm
getForm(group: FormGroup, fields: any[]) {
for (let f of fields) {
switch (f.type) {
case "group":
...
break;
case "array":
....
break;
default:
...
break;
}
}
}
person
Eliseo
schedule
15.04.2021