使用可重复输入从 json 构建表单:无法使用路径 formArray 找到控件
Building form from json with repeatable inputs: Cannot find control with path formArray
我正在尝试基于 json 构建表单。我有一个使用 Spring 引导的后端 returns 以下对象到 angular 前端应用程序:
{
"controls": [
{
"name": "genre",
"type": "radio",
"radioOptions": [
{ "key": "1", "value": "Mr." },
{ "key": "2", "value": "Ms." }
],
"validators": {}
},
{
"name": "firstName",
"label": "First name:",
"value": "",
"type": "text",
"repeat": false,
"validators": {
"required": true,
"minLength": 10
}
},
{
"name": "lastName",
"label": "Last name:",
"value": "",
"type": "text",
"repeat": false,
"validators": {}
},
{
"name": "softwareTopics",
"label": "Softwares:",
"value": "",
"type": "text",
"repeat": true,
"maxRepeat": 5,
"validators": {
"required": true,
"minLength": 10
}
},
{
"name": "hobbies",
"label": "Hobbies",
"value": "",
"type": "select",
"selected": "Select your hobbies",
"multi": false,
"selectOptions": [
{ "key": "1", "value": "Tennis" },
{ "key": "2", "value": "Golf" },
{ "key": "3", "value": "Bike"}
],
"validators": {}
},
{
"name": "time",
"label": "Time",
"value": "",
"type": "time",
"validators": {}
},
{
"name": "date",
"label": "Date",
"value": "",
"type": "date",
"validators": {}
},
{
"name": "comments",
"label": "Comments",
"value": "",
"type": "textarea",
"validators": {}
},
{
"name": "agreeTerms",
"label": "This is a checkbox?",
"value": "false",
"type": "checkbox",
"validators": {}
},
{
"name": "size",
"label": "Size",
"value": "",
"type": "range",
"options": {
"min": "0",
"max": "100",
"step": "1"
},
"validators": {}
},
{
"name": "toggle",
"label": "Do you like toggles?",
"value": "false",
"type": "toggle",
"validators": {}
}
]
}
在这个 JSON 中,我尝试使用尽可能多的输入类型。大多数代码实际上都有效。在我的组件中,我只需要调用我的 rest API that returns me my JSON object.
ngOnInit(): void {
// loading json response from back
this.apiService.getForm().subscribe(
(response: any) => {
this.jsonResponse = response
this.buildForm((this.jsonResponse.controls))
},
(error: any) => {
console.log(error)
},
() => {
console.log("Done");
}
)
}
从那里我尝试检查我的 Json 以便动态生成我的表单。为此,我将响应发送到 buildForm 方法。
buildForm(controls: JsonFormControls[]): void {
// we will loop all entries of JsonFormControls objects from the controls array
console.log("controls", controls);
let repeatedInputFormGroup = this.fb.group({});
for (const control of controls) {
// some inputs have one or more validators: input can be required, have a min length, max length...
const controlValidators = [];
// a control has a key and a value.
// example: "validators": { "required": true, "minLength": 10 }
// this snippet is reusable: can be optimized if used in many forms
for (const [key, value] of Object.entries(control.validators)) {
switch (key) {
case 'min':
controlValidators.push(Validators.min(value));
break;
case 'max':
controlValidators.push(Validators.max(value));
break;
case 'required':
if (value) {
controlValidators.push(Validators.required);
}
break;
case 'requiredTrue':
if (value) {
controlValidators.push(Validators.requiredTrue);
}
break;
case 'email':
if (value) {
controlValidators.push(Validators.email);
}
break;
case 'minLength':
controlValidators.push(Validators.minLength(value));
break;
case 'maxLength':
controlValidators.push(Validators.maxLength(value));
break;
case 'pattern':
controlValidators.push(Validators.pattern(value));
break;
case 'nullValidator':
if (value) {
controlValidators.push(Validators.nullValidator);
}
break;
default:
break;
}
}
// we must handle repeated inputs
repeatedInputFormGroup = this.fb.group({});
if (control.repeat) {
repeatedInputFormGroup = this.fb.group({
responses: this.fb.array([this.fb.group({response:''})])
})
}
// we add a new control and pass an array of validators
this.form.addControl(
control.name,
this.fb.control(control.value, controlValidators)
);
this.form.controls = { ...this.form.controls, ...repeatedInputFormGroup.controls}
}
}
我还有一些要复制的输入。为此,我尝试将它们添加到名为 repeatedInputFormGroup 的 formGroup 中。最后,我使用传播运算符来最终构建我的表单。在这个阶段我不检查添加了多少项目(maxRepeat 值)。
我实例化了这些方法以允许用户添加新输入或删除它。
// getter
get items(): FormArray {
return this.form.get('responses') as FormArray;
}
addInputItem(): void {
this.items.push(this.fb.group({response:''}));
}
deleteInputItem(index: number): void {
this.items.removeAt(index);
}
我的提交方法除了一些 console.log
没有做任何特别的事情
onSubmit(): void {
console.log("Form is valid: " + this.form.valid);
console.log("Form values: " + this.form.value);
}
在我的 HTML 内容中,我检查收到的表格并构建输入
<!-- creating the form and loop -->
<span *ngIf="form != null">
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div *ngFor="let control of jsonResponse.controls">
<div class="mb-3">
<span *ngIf="control.label != '' && control.type !== 'toggle' && control.type !== 'checkbox'">
<label class="form-label">{{ control.label }}</label>
</span>
<!-- for inputs that are not repeatable -->
<span *ngIf="inputTypes.includes(control.type) && control.repeat === false">
<input
[type]="control.type"
[formControlName]="control.name"
[value]="control.value"
class="form-control"
/>
</span>
<!-- for inputs that are not repeatable -->
<span *ngIf="inputTypes.includes(control.type) && control.repeat === true">
<div formArrayName="{{ control.name }}">
<div
*ngFor="let item of items.controls; let id = index"
class="input-group mb-3"
[formGroupName]="id">
<input
class="form-control"
formControlName="{{ control.name }}"/>
<button type="button" class="btn btn-outline-secondary"
(click)="deleteInputItem(id)">Remove</button>
</div>
<button type="button" class="btn btn-primary" (click)="addInputItem()">Add entry</button>
</div>
</span>
<!-- text area -->
<span *ngIf="control.type === 'textarea'">
<textarea
[formControlName]="control.name"
[value]="control.value"
class="form-control"
></textarea>
</span>
<!-- select -->
<span *ngIf="control.type === 'select'">
<select
[formControlName]="control.name"
class="form-select form-select-lg mb-3">
<option selected>{{ control.selected }}</option>
<option *ngFor="let option of control.selectOptions"
value="{{ option.key }}"> {{ option.value }}</option>
</select>
</span>
<!-- range -->
<span *ngIf="control.type === 'range'">
<label for="{{control.name}}" class="form-label">{{control.label}}</label>
<input
*ngIf="control.type === 'range'"
type="range"
[min]="control.options?.min"
[max]="control.options?.max"
[formControlName]="control.name"
class="form-range"
id="{{control.name}}"
/>
</span>
<!-- handling checkboxes -->
<span *ngIf="control.type === 'checkbox'">
<input class="form-check-input" type="checkbox" [id]="control.name"/>
<label class="form-check-label">{{ control.label }}</label>
</span>
<!-- toggle -->
<span *ngIf="control.type === 'toggle'">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="{{ control.name }}">
<label class="form-check-label" for="{{ control.name }}">Default switch checkbox input</label>
</div>
</span>
<!-- radio buttons -->
<span *ngIf="control.type === 'radio'">
<div class="form-check form-check-inline" *ngFor="let option of control.radioOptions">
<input class="form-check-input" type="radio" name="{{control.name }}"
id="{{option.key}}-{{option.value}}" value="{{option.value}}">
<label class="form-check-label" for="{{option.key}}-{{option.value}}">{{option.value}}</label>
</div>
</span>
</div>
</div>
<div>
<button
class="btn btn-primary"
type="submit">
Submit
</button>
</div>
</form>
</span>
我似乎对可以使用“添加”按钮添加的输入有疑问。首先在加载表单时,我没有检索整个表单并收到错误消息。
core.mjs:6469 ERROR Error: Cannot find control with path: 'softwareTopic -> 0'
at _throwError (forms.mjs:1779)
at setUpFormContainer (forms.mjs:1752)
at FormGroupDirective._setUpFormContainer (forms.mjs:5437)
at FormGroupDirective.addFormGroup (forms.mjs:5327)
at FormGroupName.ngOnInit (forms.mjs:4189)
at callHook (core.mjs:2526)
at callHooks (core.mjs:2495)
at executeInitAndCheckHooks (core.mjs:2446)
at selectIndexInternal (core.mjs:8390)
我还注意到在提交表单时我的单选按钮没有设置。如果它能工作,这是一个很酷的东西。
对于重复控件,我们可以使用 FormArray 来表示它们,而不是使用另一个 FormControl repeatedInputFormGroup
。下面是可以在 buildForm()
中的 switch
块之后添加的代码:
// we must handle repeated inputs
const formControl = this.fb.control(control.value, controlValidators);
if (control.repeat) {
this.form.addControl(control.name, this.fb.array([formControl]));
} else {
this.form.addControl(control.name, formControl);
}
我们可以修改addInputItem
和deleteInputItem
为:
addInputItem(name: string): void {
// Haven't taken care of setting validators
(<FormArray>this.form.get(name)).push(this.fb.control(''));
}
deleteInputItem(name: string, index: number): void {
(<FormArray>this.form.get(name)).removeAt(index);
}
HTML为重复控制:
<span *ngIf="inputTypes.includes(control.type) && control.repeat === true">
<div formArrayName="{{ control.name }}">
<div *ngFor="let item of form.get(control.name)['controls']; let id = index"
class="input-group mb-3">
<input class="form-control" formControlName="{{ id }}" />
<button
type="button"
class="btn btn-outline-secondary"
(click)="deleteInputItem(control.name, id)">
Remove
</button>
</div>
<button
type="button"
class="btn btn-primary"
(click)="addInputItem(control.name)">
Add entry
</button>
</div>
</span>
关于未设置单选按钮,您需要添加 formControlName
指令作为:
<input class="form-check-input" type="radio" name="{{ control.name }}"
value="{{ option.value }}" id="{{ option.key }}-{{ option.value }}"
[formControlName]="control.name"/>
我正在尝试基于 json 构建表单。我有一个使用 Spring 引导的后端 returns 以下对象到 angular 前端应用程序:
{
"controls": [
{
"name": "genre",
"type": "radio",
"radioOptions": [
{ "key": "1", "value": "Mr." },
{ "key": "2", "value": "Ms." }
],
"validators": {}
},
{
"name": "firstName",
"label": "First name:",
"value": "",
"type": "text",
"repeat": false,
"validators": {
"required": true,
"minLength": 10
}
},
{
"name": "lastName",
"label": "Last name:",
"value": "",
"type": "text",
"repeat": false,
"validators": {}
},
{
"name": "softwareTopics",
"label": "Softwares:",
"value": "",
"type": "text",
"repeat": true,
"maxRepeat": 5,
"validators": {
"required": true,
"minLength": 10
}
},
{
"name": "hobbies",
"label": "Hobbies",
"value": "",
"type": "select",
"selected": "Select your hobbies",
"multi": false,
"selectOptions": [
{ "key": "1", "value": "Tennis" },
{ "key": "2", "value": "Golf" },
{ "key": "3", "value": "Bike"}
],
"validators": {}
},
{
"name": "time",
"label": "Time",
"value": "",
"type": "time",
"validators": {}
},
{
"name": "date",
"label": "Date",
"value": "",
"type": "date",
"validators": {}
},
{
"name": "comments",
"label": "Comments",
"value": "",
"type": "textarea",
"validators": {}
},
{
"name": "agreeTerms",
"label": "This is a checkbox?",
"value": "false",
"type": "checkbox",
"validators": {}
},
{
"name": "size",
"label": "Size",
"value": "",
"type": "range",
"options": {
"min": "0",
"max": "100",
"step": "1"
},
"validators": {}
},
{
"name": "toggle",
"label": "Do you like toggles?",
"value": "false",
"type": "toggle",
"validators": {}
}
]
}
在这个 JSON 中,我尝试使用尽可能多的输入类型。大多数代码实际上都有效。在我的组件中,我只需要调用我的 rest API that returns me my JSON object.
ngOnInit(): void {
// loading json response from back
this.apiService.getForm().subscribe(
(response: any) => {
this.jsonResponse = response
this.buildForm((this.jsonResponse.controls))
},
(error: any) => {
console.log(error)
},
() => {
console.log("Done");
}
)
}
从那里我尝试检查我的 Json 以便动态生成我的表单。为此,我将响应发送到 buildForm 方法。
buildForm(controls: JsonFormControls[]): void {
// we will loop all entries of JsonFormControls objects from the controls array
console.log("controls", controls);
let repeatedInputFormGroup = this.fb.group({});
for (const control of controls) {
// some inputs have one or more validators: input can be required, have a min length, max length...
const controlValidators = [];
// a control has a key and a value.
// example: "validators": { "required": true, "minLength": 10 }
// this snippet is reusable: can be optimized if used in many forms
for (const [key, value] of Object.entries(control.validators)) {
switch (key) {
case 'min':
controlValidators.push(Validators.min(value));
break;
case 'max':
controlValidators.push(Validators.max(value));
break;
case 'required':
if (value) {
controlValidators.push(Validators.required);
}
break;
case 'requiredTrue':
if (value) {
controlValidators.push(Validators.requiredTrue);
}
break;
case 'email':
if (value) {
controlValidators.push(Validators.email);
}
break;
case 'minLength':
controlValidators.push(Validators.minLength(value));
break;
case 'maxLength':
controlValidators.push(Validators.maxLength(value));
break;
case 'pattern':
controlValidators.push(Validators.pattern(value));
break;
case 'nullValidator':
if (value) {
controlValidators.push(Validators.nullValidator);
}
break;
default:
break;
}
}
// we must handle repeated inputs
repeatedInputFormGroup = this.fb.group({});
if (control.repeat) {
repeatedInputFormGroup = this.fb.group({
responses: this.fb.array([this.fb.group({response:''})])
})
}
// we add a new control and pass an array of validators
this.form.addControl(
control.name,
this.fb.control(control.value, controlValidators)
);
this.form.controls = { ...this.form.controls, ...repeatedInputFormGroup.controls}
}
}
我还有一些要复制的输入。为此,我尝试将它们添加到名为 repeatedInputFormGroup 的 formGroup 中。最后,我使用传播运算符来最终构建我的表单。在这个阶段我不检查添加了多少项目(maxRepeat 值)。
我实例化了这些方法以允许用户添加新输入或删除它。
// getter
get items(): FormArray {
return this.form.get('responses') as FormArray;
}
addInputItem(): void {
this.items.push(this.fb.group({response:''}));
}
deleteInputItem(index: number): void {
this.items.removeAt(index);
}
我的提交方法除了一些 console.log
没有做任何特别的事情onSubmit(): void {
console.log("Form is valid: " + this.form.valid);
console.log("Form values: " + this.form.value);
}
在我的 HTML 内容中,我检查收到的表格并构建输入
<!-- creating the form and loop -->
<span *ngIf="form != null">
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div *ngFor="let control of jsonResponse.controls">
<div class="mb-3">
<span *ngIf="control.label != '' && control.type !== 'toggle' && control.type !== 'checkbox'">
<label class="form-label">{{ control.label }}</label>
</span>
<!-- for inputs that are not repeatable -->
<span *ngIf="inputTypes.includes(control.type) && control.repeat === false">
<input
[type]="control.type"
[formControlName]="control.name"
[value]="control.value"
class="form-control"
/>
</span>
<!-- for inputs that are not repeatable -->
<span *ngIf="inputTypes.includes(control.type) && control.repeat === true">
<div formArrayName="{{ control.name }}">
<div
*ngFor="let item of items.controls; let id = index"
class="input-group mb-3"
[formGroupName]="id">
<input
class="form-control"
formControlName="{{ control.name }}"/>
<button type="button" class="btn btn-outline-secondary"
(click)="deleteInputItem(id)">Remove</button>
</div>
<button type="button" class="btn btn-primary" (click)="addInputItem()">Add entry</button>
</div>
</span>
<!-- text area -->
<span *ngIf="control.type === 'textarea'">
<textarea
[formControlName]="control.name"
[value]="control.value"
class="form-control"
></textarea>
</span>
<!-- select -->
<span *ngIf="control.type === 'select'">
<select
[formControlName]="control.name"
class="form-select form-select-lg mb-3">
<option selected>{{ control.selected }}</option>
<option *ngFor="let option of control.selectOptions"
value="{{ option.key }}"> {{ option.value }}</option>
</select>
</span>
<!-- range -->
<span *ngIf="control.type === 'range'">
<label for="{{control.name}}" class="form-label">{{control.label}}</label>
<input
*ngIf="control.type === 'range'"
type="range"
[min]="control.options?.min"
[max]="control.options?.max"
[formControlName]="control.name"
class="form-range"
id="{{control.name}}"
/>
</span>
<!-- handling checkboxes -->
<span *ngIf="control.type === 'checkbox'">
<input class="form-check-input" type="checkbox" [id]="control.name"/>
<label class="form-check-label">{{ control.label }}</label>
</span>
<!-- toggle -->
<span *ngIf="control.type === 'toggle'">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="{{ control.name }}">
<label class="form-check-label" for="{{ control.name }}">Default switch checkbox input</label>
</div>
</span>
<!-- radio buttons -->
<span *ngIf="control.type === 'radio'">
<div class="form-check form-check-inline" *ngFor="let option of control.radioOptions">
<input class="form-check-input" type="radio" name="{{control.name }}"
id="{{option.key}}-{{option.value}}" value="{{option.value}}">
<label class="form-check-label" for="{{option.key}}-{{option.value}}">{{option.value}}</label>
</div>
</span>
</div>
</div>
<div>
<button
class="btn btn-primary"
type="submit">
Submit
</button>
</div>
</form>
</span>
我似乎对可以使用“添加”按钮添加的输入有疑问。首先在加载表单时,我没有检索整个表单并收到错误消息。
core.mjs:6469 ERROR Error: Cannot find control with path: 'softwareTopic -> 0'
at _throwError (forms.mjs:1779)
at setUpFormContainer (forms.mjs:1752)
at FormGroupDirective._setUpFormContainer (forms.mjs:5437)
at FormGroupDirective.addFormGroup (forms.mjs:5327)
at FormGroupName.ngOnInit (forms.mjs:4189)
at callHook (core.mjs:2526)
at callHooks (core.mjs:2495)
at executeInitAndCheckHooks (core.mjs:2446)
at selectIndexInternal (core.mjs:8390)
我还注意到在提交表单时我的单选按钮没有设置。如果它能工作,这是一个很酷的东西。
对于重复控件,我们可以使用 FormArray 来表示它们,而不是使用另一个 FormControl repeatedInputFormGroup
。下面是可以在 buildForm()
中的 switch
块之后添加的代码:
// we must handle repeated inputs
const formControl = this.fb.control(control.value, controlValidators);
if (control.repeat) {
this.form.addControl(control.name, this.fb.array([formControl]));
} else {
this.form.addControl(control.name, formControl);
}
我们可以修改addInputItem
和deleteInputItem
为:
addInputItem(name: string): void {
// Haven't taken care of setting validators
(<FormArray>this.form.get(name)).push(this.fb.control(''));
}
deleteInputItem(name: string, index: number): void {
(<FormArray>this.form.get(name)).removeAt(index);
}
HTML为重复控制:
<span *ngIf="inputTypes.includes(control.type) && control.repeat === true">
<div formArrayName="{{ control.name }}">
<div *ngFor="let item of form.get(control.name)['controls']; let id = index"
class="input-group mb-3">
<input class="form-control" formControlName="{{ id }}" />
<button
type="button"
class="btn btn-outline-secondary"
(click)="deleteInputItem(control.name, id)">
Remove
</button>
</div>
<button
type="button"
class="btn btn-primary"
(click)="addInputItem(control.name)">
Add entry
</button>
</div>
</span>
关于未设置单选按钮,您需要添加 formControlName
指令作为:
<input class="form-check-input" type="radio" name="{{ control.name }}"
value="{{ option.value }}" id="{{ option.key }}-{{ option.value }}"
[formControlName]="control.name"/>