我知道将自定义控件创建为组件,但我无法弄清楚如何创建自定义组。
我们可以通过implementing ControlValueAccessor
并使用<my-cmp formControlName="foo"></my-cmp>
之类的自定义组件执行此操作,我们如何才能为a group实现此效果?
<my-cmp formGroupName="aGroup"></my-cmp>
两个非常常见的用例是:(a)将长格式分为几个步骤,每个步骤分别在一个单独的组件中;(b)封装一组字段,这些字段以多种形式出现,例如地址(国家组,州,城市,地址,建筑物编号)或出生日期(年,月,日)。
Parent使用FormBuilder
构建了以下表单:
// parent model
form = this.fb.group({
username: '',
fullName: '',
password: '',
address: this.fb.group({
country: '',
state: '',
city: '',
street: '',
building: '',
})
})
父模板(为简洁起见,不可访问和非语义):
<!-- parent template -->
<form [groupName]="form">
<input formControlName="username">
<input formControlName="fullName">
<input formControlName="password">
<address-form-group formGroup="address"></address-form-group>
</form>
现在这个AddressFormGroupComponent
知道如何处理其中包含这些特定控件的组。
<!-- child template -->
<input formControlName="country">
<input formControlName="state">
<input formControlName="city">
<input formControlName="street">
<input formControlName="building">
答案 0 :(得分:26)
rusev's answer中提到了我遗失的文章,即注入ControlContainer
。
事实证明,如果将formGroupName
放在组件上,并且该组件注入ControlContainer
,则会获得对包含该表单的容器的引用。从这里开始很容易。
我们创建一个子表单组件。
@Component({
selector: 'sub-form',
template: `
<ng-container [formGroup]="controlContainer.control">
<input type=text formControlName=foo>
<input type=text formControlName=bar>
</ng-container>
`,
})
export class SubFormComponent {
constructor(public controlContainer: ControlContainer) {
}
}
注意我们需要输入的包装器。我们不想要表格,因为这已经在表格内。所以我们使用ng-container
。这将远离最终的DOM,因此没有不必要的元素。
现在我们可以使用这个组件了。
@Component({
selector: 'my-app',
template: `
<form [formGroup]=form>
<sub-form formGroupName=group></sub-form>
<input type=text formControlName=baz>
</form>
`,
})
export class AppComponent {
form = this.fb.group({
group: this.fb.group({
foo: 'foo',
bar: 'bar',
}),
baz: 'baz',
})
constructor(private fb: FormBuilder) {}
}
您可以看到live demo on StackBlitz。
这是对rusev在几个方面的答案的改进:
groupName
输入;相反,我们使用Angular提供的formGroupName
@SkipSelf
装饰器,因为我们不注入父控件,但我们需要的是group.control.get(groupName)
这是为了抓住自己而去父母。答案 1 :(得分:2)
对于表单控件名称,Angular表单没有组名的概念。但是,您可以通过将子模板包装在表单组中来轻松解决此问题。
以下示例类似于您发布的标记 - https://plnkr.co/edit/2AZ3Cq9oWYzXeubij91I?p=preview
@Component({
selector: 'address-form-group',
template: `
<!-- child template -->
<ng-container [formGroup]="group.control.get(groupName)">
<input formControlName="country">
<input formControlName="state">
<input formControlName="city">
<input formControlName="street">
<input formControlName="building">
</ng-container>
`
})
export class AddressFormGroupComponent {
@Input() public groupName: string;
constructor(@SkipSelf() public group: ControlContainer) { }
}
@Component({
selector: 'my-app',
template: `
<!-- parent template -->
<div [formGroup]="form">
<input formControlName="username">
<input formControlName="fullName">
<input formControlName="password">
<address-form-group groupName="address"></address-form-group>
</div>
{{form?.value | json}}
`
})
export class AppComponent {
public form: FormGroup;
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
username: '',
fullName: '',
password: '',
address: this.fb.group({
country: '',
state: '',
city: '',
street: '',
building: '',
})
});
}
}
答案 2 :(得分:0)
我找到了一种动态的方法,可以使用反应表单。
关于这个主题的问题和帖子的研究,我找到了构建一个绑定父表单的指令和动态子组件的材料。好的一点是,您不需要在父组件中定义所有表单,但每个FormGroup都是独立的,并且与指令有效。
我将其命名为BindFormDirective
,它会收到parent
和child
个实施BindForm
界面的组件(他们有公开form: FormGroup
成员操纵),他们为自己提供BINDFORM_TOKEN
。
该指令接收一个值作为子组的名称,其代码如下:
import { ChangeDetectorRef, Directive, Inject, InjectionToken, Input, OnDestroy, OnInit, Self, SkipSelf } from '@angular/core';
import { FormGroup } from '@angular/forms';
export interface BindForm {
form: FormGroup;
}
export const BINDFORM_TOKEN = new InjectionToken<BindForm>('BindFormToken');
@Directive({
selector: '[bindForm]'
})
export class BindFormDirective implements OnInit, OnDestroy {
private controlName = null;
@Input()
set binForm(value) {
if (this.controlName) {
throw new Error('Cannot change the bindName on runtime!');
}
this.controlName = value;
}
constructor(
private cdr: ChangeDetectorRef,
@Inject(BINDFORM_TOKEN) @SkipSelf() private parent: BindForm,
@Inject(BINDFORM_TOKEN) @Self() private child: BindForm
) {}
ngOnInit() {
if (!this.controlName) {
throw new Error('BindForm directive requires a value to be used as the subgroup name!');
}
if (this.parent.form.get(this.controlName)) {
throw new Error(`That name (${this.controlName}) already exists on the parent form!`);
}
// add a child control under the unique name
this.parent.form.addControl(this.controlName, this.child.form);
this.cdr.detectChanges();
}
ngOnDestroy() {
// remove the component from the parent
this.parent.form.removeControl(this.controlName);
}
}
另一方面,所涉及的组件应该在BINDFORM_TOKEN
定义中提供@Component
,可以通过指令注入,并实现BindForm
接口,如:
@Component({
...
providers: [
{
provide: BINDFORM_TOKEN,
useExisting: forwardRef(() => MyFormComponent)
}
]
})
export class MyFormComponent implements BindForm, OnInit {
form: FormGroup;
...
因此,您可以单独实现多个表单组件,并相互绑定FormGroups,只需在父表单组件上使用该指令:
<form [formGroup]="form" ...>
<my-step1 bindForm="step1"></my-step1>
</form>
如果完整插图需要剩下的代码,我会花一些额外的时间来更新我的答案。 享受!