Angular 可重用子组件中的反应式表单:FormArrays 问题:`formGroupName` 必须与父 formGroup 指令一起使用
Angular Reactive Forms in Reusable Child Components: Problem with FormArrays: `formGroupName` must be used with a parent formGroup directive
我正在尝试通过示例来描述问题 (full example on stackblitz)
如果我尝试以带有子组件的简单 "formControls" 或 "formGroups" 形式放置反应形式的某些部分,则没有问题。 (参见上面关于 stackblitz 的示例)。 FormGroupDirective
按预期工作。
但是如果我尝试将 FormArray 放置在子组件中,我会遇到麻烦,因为:
<div [formGroupName]="i">
<!--
Error: formGroupName must be used
with a parent formGroup directive.
-->
<div>
Reactive-Form 是一种简单的形式:
ngOnInit () {
this.newForm = this.fb.group({
id: [{value: '4711', disabled: this.idReadOnly}, Validators.required],
chapter: this.fb.group({
name: ['Some Chapter', Validators.required]
}),
faq: this.fb.array(this.faqArray)
});
}
正如上面已经提到的 id
和 chapter
没有问题,它们是在自定义子组件中实现的,例如:
<fe-input
[controlPath]="['id']"
placeholder="Child-FormControl ID"
></fe-input>
<my-form-group
[controlPath]="['chapter', 'name']"
placeholder="Child-FormGroup Chapter"
[required]="true"
>
</my-form-group>
在 stackblitz 上的应用程序中,您将看到工作部分 'ID' 和 'Chapter'。
与formArray
相同的方法:
<my-form-array
[controlPath]="['faq']"
placeholder="Child-FormArray FAQ"
[required]="true"
></my-form-array>
应该按我的预期工作,但是 <div [formGroupName]="i">
部分仅在子组件内导致上面已经提到的错误:
Error: formGroupName must be used
with a parent formGroup directive.
如果我在原始文件中使用它(定义反应形式的地方),它可以正常工作。
我很困惑,也许我只是忽略了一个简单的问题?有人能帮我吗?整个示例可在此处在线获取:( >> stackblitz)
更新 1:
我已经将@yurzui 的解决方案与
集成
viewProviders: [{
provide: ControlContainer,
useExisting: FormGroupDirective
}]
错误“Error: formGroupName must be used..
”消失了。之后,我为formArray字段集成了自定义组件,它们无法获取控件值。我想我已经接近解决方案了。那是自定义组件:
import { Component, OnInit, Input } from '@angular/core';
import {
FormControl,
FormGroupDirective
} from '@angular/forms';
@Component({
selector: 'fe-input',
template: `
<mat-form-field>
<input
matInput
[placeholder]="placeholder"
[formControl]="control"
[required]="required"
/>
<mat-error>
This is a required field!
</mat-error>
</mat-form-field>
`,
styles: [
]
})
export class FormElementInputComponent implements OnInit {
// Values to be set:
@Input() controlPath: any;
@Input() placeholder: string;
@Input() controlValue: any;
@Input() required = false;
// That's the reuseable control
control: FormControl;
constructor(private fgd: FormGroupDirective) {}
ngOnInit() {
this.control = this.fgd.control.get(
this.controlPath
) as FormControl;
}
}
最后是 my-form-array
的模板部分:
template: `
<div fxLayout="column">
<div *ngFor="let c of control.controls; index as i">
<div [formGroupName]="i">
<fe-input
[controlPath]="q"
placeholder="Question"
[required]="required"
></fe-input>
<fe-input
[controlPath]="a"
placeholder="Answer"
[required]="required"
></fe-input>
<div>
</div>
</div>
`,
所以在你的子组件中,你必须使用 formArrayName="faq"
,你将使用 FormGroupDirective
从父组件获取它,而在子组件的 viewProviders 数组中,你必须像这样使用 useExisting
viewProviders: [
{ provide: ControlContainer, useExisting: FormGroupDirective }
]
在你的html
<div formArrayName="faq" *ngFor="let fa of faq['controls']; let faqIndex = index;">
<div [formGroupName]="faqIndex">
// here goes your code
</div>
</div>
首先,您的 FormArray 是 FormControls 的 FormArray,它们获取对象的值,而不是 FormGroup 的 FormArray。创建表单时必须更改
this.newForm = this.fb.group({
id: [{value: '4711', disabled: this.idReadOnly}, Validators.required],
chapter: this.fb.group({
name: ['Some Chapter', Validators.required]
}),
faq: this.fb.array(this.faqArray.map(x=>this.fb.group({
q:x.q,
a:x.a
})))
});
其次,您可以使用 "other way" 来处理不使用 [formGroupName="i"] 的 FormGroup 的 FormArray,否则 [formGroup]="control"。是的
<!--remove this that not work
<div *ngFor="let c of control.controls; index as i">
<div [formGroupName]="i">
....
<div>
</div>
-->
<!--use [formGroup] -->
<div *ngFor="let c of control.controls; index as i">
<div [formGroup]="c">
....
</div>
</div>
那么,在 formGroup 中,您需要 fe-input [controlPath]="'q'" 和 [controlPath]="'a'"
我删除了 form-group 因为它也是错误的,尝试使用 [formGroup] 并使用 viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }]
你看到你的 forked stackblitz
更新 如果我们使用
标记 formArray
<my-form-array
[controlPath]="['faq','a','q']"
placeholder="Child-FormArray FAQ"
[required]="true"
></my-form-array>
我们可以在 ngOnInit
中更改 my-form-array
this.control = this.fgd.control.get(
this.controlPath[0]
) as FormArray;
还有.html
<div *ngFor="let c of control.controls; index as i">
<div [formGroup]="c">
<fe-input *ngFor="let fields of controlPath.slice(1)"
[controlPath]="fields"
placeholder="Question"
[required]="required"
></fe-input>
</div>
我正在尝试通过示例来描述问题 (full example on stackblitz)
如果我尝试以带有子组件的简单 "formControls" 或 "formGroups" 形式放置反应形式的某些部分,则没有问题。 (参见上面关于 stackblitz 的示例)。 FormGroupDirective
按预期工作。
但是如果我尝试将 FormArray 放置在子组件中,我会遇到麻烦,因为:
<div [formGroupName]="i">
<!--
Error: formGroupName must be used
with a parent formGroup directive.
-->
<div>
Reactive-Form 是一种简单的形式:
ngOnInit () {
this.newForm = this.fb.group({
id: [{value: '4711', disabled: this.idReadOnly}, Validators.required],
chapter: this.fb.group({
name: ['Some Chapter', Validators.required]
}),
faq: this.fb.array(this.faqArray)
});
}
正如上面已经提到的 id
和 chapter
没有问题,它们是在自定义子组件中实现的,例如:
<fe-input
[controlPath]="['id']"
placeholder="Child-FormControl ID"
></fe-input>
<my-form-group
[controlPath]="['chapter', 'name']"
placeholder="Child-FormGroup Chapter"
[required]="true"
>
</my-form-group>
在 stackblitz 上的应用程序中,您将看到工作部分 'ID' 和 'Chapter'。
与formArray
相同的方法:
<my-form-array
[controlPath]="['faq']"
placeholder="Child-FormArray FAQ"
[required]="true"
></my-form-array>
应该按我的预期工作,但是 <div [formGroupName]="i">
部分仅在子组件内导致上面已经提到的错误:
Error: formGroupName must be used
with a parent formGroup directive.
如果我在原始文件中使用它(定义反应形式的地方),它可以正常工作。
我很困惑,也许我只是忽略了一个简单的问题?有人能帮我吗?整个示例可在此处在线获取:( >> stackblitz)
更新 1:
我已经将@yurzui 的解决方案与
集成
viewProviders: [{
provide: ControlContainer,
useExisting: FormGroupDirective
}]
错误“Error: formGroupName must be used..
”消失了。之后,我为formArray字段集成了自定义组件,它们无法获取控件值。我想我已经接近解决方案了。那是自定义组件:
import { Component, OnInit, Input } from '@angular/core';
import {
FormControl,
FormGroupDirective
} from '@angular/forms';
@Component({
selector: 'fe-input',
template: `
<mat-form-field>
<input
matInput
[placeholder]="placeholder"
[formControl]="control"
[required]="required"
/>
<mat-error>
This is a required field!
</mat-error>
</mat-form-field>
`,
styles: [
]
})
export class FormElementInputComponent implements OnInit {
// Values to be set:
@Input() controlPath: any;
@Input() placeholder: string;
@Input() controlValue: any;
@Input() required = false;
// That's the reuseable control
control: FormControl;
constructor(private fgd: FormGroupDirective) {}
ngOnInit() {
this.control = this.fgd.control.get(
this.controlPath
) as FormControl;
}
}
最后是 my-form-array
的模板部分:
template: `
<div fxLayout="column">
<div *ngFor="let c of control.controls; index as i">
<div [formGroupName]="i">
<fe-input
[controlPath]="q"
placeholder="Question"
[required]="required"
></fe-input>
<fe-input
[controlPath]="a"
placeholder="Answer"
[required]="required"
></fe-input>
<div>
</div>
</div>
`,
所以在你的子组件中,你必须使用 formArrayName="faq"
,你将使用 FormGroupDirective
从父组件获取它,而在子组件的 viewProviders 数组中,你必须像这样使用 useExisting
viewProviders: [
{ provide: ControlContainer, useExisting: FormGroupDirective }
]
在你的html
<div formArrayName="faq" *ngFor="let fa of faq['controls']; let faqIndex = index;">
<div [formGroupName]="faqIndex">
// here goes your code
</div>
</div>
首先,您的 FormArray 是 FormControls 的 FormArray,它们获取对象的值,而不是 FormGroup 的 FormArray。创建表单时必须更改
this.newForm = this.fb.group({
id: [{value: '4711', disabled: this.idReadOnly}, Validators.required],
chapter: this.fb.group({
name: ['Some Chapter', Validators.required]
}),
faq: this.fb.array(this.faqArray.map(x=>this.fb.group({
q:x.q,
a:x.a
})))
});
其次,您可以使用 "other way" 来处理不使用 [formGroupName="i"] 的 FormGroup 的 FormArray,否则 [formGroup]="control"。是的
<!--remove this that not work
<div *ngFor="let c of control.controls; index as i">
<div [formGroupName]="i">
....
<div>
</div>
-->
<!--use [formGroup] -->
<div *ngFor="let c of control.controls; index as i">
<div [formGroup]="c">
....
</div>
</div>
那么,在 formGroup 中,您需要 fe-input [controlPath]="'q'" 和 [controlPath]="'a'"
我删除了 form-group 因为它也是错误的,尝试使用 [formGroup] 并使用 viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }]
你看到你的 forked stackblitz
更新 如果我们使用
标记 formArray<my-form-array
[controlPath]="['faq','a','q']"
placeholder="Child-FormArray FAQ"
[required]="true"
></my-form-array>
我们可以在 ngOnInit
中更改 my-form-arraythis.control = this.fgd.control.get(
this.controlPath[0]
) as FormArray;
还有.html
<div *ngFor="let c of control.controls; index as i">
<div [formGroup]="c">
<fe-input *ngFor="let fields of controlPath.slice(1)"
[controlPath]="fields"
placeholder="Question"
[required]="required"
></fe-input>
</div>