使用 Reactive Forms 将 formControlName 转发到 Angular 中的内部组件
Forwarding formControlName to inner component in Angular with Reactive Forms
我有一个自定义控件组件 <some-input>
,我包装到 <ext-some-input>
。 SomeInput 被封装,拥有自己的 API 并支持反应形式。
ExtSomeInput 创建为 SomeInput.
的高级包装器
我关注html:
<form [formGroup]="form">
<ext-some-input formControlName="name">
</form>
和ExtSomeInput的html:
<some-input formControlName="_???_"></some-input>
问题是如何将formControlName转发给内部SomeInput组件?我需要将表单和内部 formControl 绑定起来。这可能吗?
已编辑:
我创建了带有这个问题的 stackblitz 项目:here
You can create a @Input in component ext-some-input
ext-some-input.ts
@Input
formControlName
ext-some-input.html
<some-input [formControlName]="formControlName"></some-input>
我认为您实际上看起来比 input
复杂一点。
如果您想创建自定义输入,您可以创建一个 class,这将是一个 ControlValueAccessor
(自定义表单控件)。
请查看以下文章,其中解释得非常好:
我 运行 遇到过类似的情况,我不想将 formControlName 传递给包装器组件。我首选的解决方法是在包装器组件中重用从父表单传递的 formGroup。您可以通过在 ExtSomeInput 组件的构造函数中注入 ControlContainer 来做到这一点:
ParentForm.component.html
<form [formGroup]="form">
<ext-some-input controlName="name">
</form>
ExtSomeInput.component.ts
// pass the formControlName as string to wrapper
@Input public controlName: string;
public form: FormGroup;
constructor(public controlContainer: ControlContainer) {}
ngOnInit() {
this.form = <FormGroup>this.controlContainer.control;
}
ExtSomeInput.component.html
// use ng-container to omit this from the DOM
<ng-container [formGroup]="form">
// wrapper markup here
<some-input [formControlName]="controlName"></some-input>
</ng-container>
您的内部组件可以使用 @Input
controlName
,但开箱即用:
Error: formControlName must be used with a parent formGroup directive.
为了将您的控件与父 FormGroup 联系起来,您可以定义 viewProvider
如下:
import { Component, Input, OnInit} from '@angular/core';
...
import { ControlContainer, FormGroupDirective } from '@angular/forms';
@Component({
...
viewProviders: [
{
provide: ControlContainer,
useExisting: FormGroupDirective
}
]
})
export class DateWrapperComponent implements OnInit {
@Input() controlName: string;
}
如果您不知道作为 ControlContainer
(FormGroupDirective
、FormGroupName
或 FormArrayName
)提供的确切指令,您可以使用更通用的方法:
viewProviders: [
{
provide: ControlContainer,
useFactory: (container: ControlContainer) => container,
deps: [[new SkipSelf(), ControlContainer]]
}
]
嘿,在这里做第二个回答,因为我们在工作中提出了一种通用方法,我们将其作为开源库发布 =)。
您可以在这里找到它:https://github.com/cloudnc/ngx-sub-form
它应该可以帮助您管理:
- 嵌套形式
- 具有多态数据的表单
- 更好的打字
自述文件中解释了所有内容,并在 /src 文件夹中提供了完整示例。 (lib其实在projects/ngx-sub-form).
此处也提供现场演示:https://cloudnc.github.io/ngx-sub-form
我遇到了几乎相同的情况和问题。 Todd Motto 的 article 让我如释重负。
他的方法是将 formGroup 作为自定义输入组件的父级。然后,从该组件,您可以将控件(验证器)和 formControlName(值)绑定到输入标签。
一如往常,榜样自己说话。
Form component ts
// Reactive Forms
form: FormGroup;
constructor(private formBuilder: FormBuilder) { }
ngOnInit() {
// Form structure and validators
this.form = this.formBuilder.group({
'user' : this.formBuilder.group({
'username' : ['', Validators.required],
'email' : ['', [Validators.required, Validators.email]]
}),
'identity' : this.formBuilder.group({
'firstname' : ['', Validators.required],
'lastname' : ['', Validators.required],
'address' : this.formBuilder.group({
'street' : ['', Validators.required],
'city' : ['', Validators.required],
})
})
});
}
onSubmit() {
// Get object with same structure as form but only with values
console.log(this.form.value);
alert('Form is ' + (this.form.invalid ? 'invalid' : 'valid'));
}
Form component html
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<form-text [formGroupParent]="form.get(['user'])"
[formGroupControlName]="'username'">
</form-text>
<form-text [formGroupParent]="form.get(['user'])"
[formGroupControlName]="'email'">
</form-text>
<hr>
<form-text [formGroupParent]="form.get(['identity'])"
[formGroupControlName]="'firstname'">
</form-text>
<form-text [formGroupParent]="form.get(['identity'])"
[formGroupControlName]="'lastname'">
</form-text>
<hr>
<form-text [formGroupParent]="form.get(['identity','address'])"
[formGroupControlName]="'street'">
</form-text>
<form-text [formGroupParent]="form.get(['identity','address'])"
[formGroupControlName]="'city'">
</form-text>
<button type="submit">Submit</button>
</form>
Custom input component ts (form-text)
// Needed to bind formControlName
@Input() formGroupParent: FormGroup;
@Input() formGroupControlName: string;
// FormControl store validators
control: FormControl;
ngOnInit() {
// Fetch Form control (validator) from FormGroup parent
this.control = <FormControl>this.formGroupParent.get(this.formGroupControlName);
}
Custom input component html (form-text)
<ng-container [formGroup]="formGroupParent">
<label>{{formGroupControlName}}</label>
<input type="text" formControlName="{{formGroupControlName}}">
</ng-container>
我有一个自定义控件组件 <some-input>
,我包装到 <ext-some-input>
。 SomeInput 被封装,拥有自己的 API 并支持反应形式。
ExtSomeInput 创建为 SomeInput.
我关注html:
<form [formGroup]="form">
<ext-some-input formControlName="name">
</form>
和ExtSomeInput的html:
<some-input formControlName="_???_"></some-input>
问题是如何将formControlName转发给内部SomeInput组件?我需要将表单和内部 formControl 绑定起来。这可能吗?
已编辑:
我创建了带有这个问题的 stackblitz 项目:here
You can create a @Input in component
ext-some-input
ext-some-input.ts
@Input
formControlName
ext-some-input.html
<some-input [formControlName]="formControlName"></some-input>
我认为您实际上看起来比 input
复杂一点。
如果您想创建自定义输入,您可以创建一个 class,这将是一个 ControlValueAccessor
(自定义表单控件)。
请查看以下文章,其中解释得非常好:
我 运行 遇到过类似的情况,我不想将 formControlName 传递给包装器组件。我首选的解决方法是在包装器组件中重用从父表单传递的 formGroup。您可以通过在 ExtSomeInput 组件的构造函数中注入 ControlContainer 来做到这一点:
ParentForm.component.html
<form [formGroup]="form">
<ext-some-input controlName="name">
</form>
ExtSomeInput.component.ts
// pass the formControlName as string to wrapper
@Input public controlName: string;
public form: FormGroup;
constructor(public controlContainer: ControlContainer) {}
ngOnInit() {
this.form = <FormGroup>this.controlContainer.control;
}
ExtSomeInput.component.html
// use ng-container to omit this from the DOM
<ng-container [formGroup]="form">
// wrapper markup here
<some-input [formControlName]="controlName"></some-input>
</ng-container>
您的内部组件可以使用 @Input
controlName
,但开箱即用:
Error: formControlName must be used with a parent formGroup directive.
为了将您的控件与父 FormGroup 联系起来,您可以定义 viewProvider
如下:
import { Component, Input, OnInit} from '@angular/core';
...
import { ControlContainer, FormGroupDirective } from '@angular/forms';
@Component({
...
viewProviders: [
{
provide: ControlContainer,
useExisting: FormGroupDirective
}
]
})
export class DateWrapperComponent implements OnInit {
@Input() controlName: string;
}
如果您不知道作为 ControlContainer
(FormGroupDirective
、FormGroupName
或 FormArrayName
)提供的确切指令,您可以使用更通用的方法:
viewProviders: [
{
provide: ControlContainer,
useFactory: (container: ControlContainer) => container,
deps: [[new SkipSelf(), ControlContainer]]
}
]
嘿,在这里做第二个回答,因为我们在工作中提出了一种通用方法,我们将其作为开源库发布 =)。
您可以在这里找到它:https://github.com/cloudnc/ngx-sub-form
它应该可以帮助您管理: - 嵌套形式 - 具有多态数据的表单 - 更好的打字
自述文件中解释了所有内容,并在 /src 文件夹中提供了完整示例。 (lib其实在projects/ngx-sub-form).
此处也提供现场演示:https://cloudnc.github.io/ngx-sub-form
我遇到了几乎相同的情况和问题。 Todd Motto 的 article 让我如释重负。
他的方法是将 formGroup 作为自定义输入组件的父级。然后,从该组件,您可以将控件(验证器)和 formControlName(值)绑定到输入标签。
一如往常,榜样自己说话。
Form component ts
// Reactive Forms
form: FormGroup;
constructor(private formBuilder: FormBuilder) { }
ngOnInit() {
// Form structure and validators
this.form = this.formBuilder.group({
'user' : this.formBuilder.group({
'username' : ['', Validators.required],
'email' : ['', [Validators.required, Validators.email]]
}),
'identity' : this.formBuilder.group({
'firstname' : ['', Validators.required],
'lastname' : ['', Validators.required],
'address' : this.formBuilder.group({
'street' : ['', Validators.required],
'city' : ['', Validators.required],
})
})
});
}
onSubmit() {
// Get object with same structure as form but only with values
console.log(this.form.value);
alert('Form is ' + (this.form.invalid ? 'invalid' : 'valid'));
}
Form component html
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<form-text [formGroupParent]="form.get(['user'])"
[formGroupControlName]="'username'">
</form-text>
<form-text [formGroupParent]="form.get(['user'])"
[formGroupControlName]="'email'">
</form-text>
<hr>
<form-text [formGroupParent]="form.get(['identity'])"
[formGroupControlName]="'firstname'">
</form-text>
<form-text [formGroupParent]="form.get(['identity'])"
[formGroupControlName]="'lastname'">
</form-text>
<hr>
<form-text [formGroupParent]="form.get(['identity','address'])"
[formGroupControlName]="'street'">
</form-text>
<form-text [formGroupParent]="form.get(['identity','address'])"
[formGroupControlName]="'city'">
</form-text>
<button type="submit">Submit</button>
</form>
Custom input component ts (form-text)
// Needed to bind formControlName
@Input() formGroupParent: FormGroup;
@Input() formGroupControlName: string;
// FormControl store validators
control: FormControl;
ngOnInit() {
// Fetch Form control (validator) from FormGroup parent
this.control = <FormControl>this.formGroupParent.get(this.formGroupControlName);
}
Custom input component html (form-text)
<ng-container [formGroup]="formGroupParent">
<label>{{formGroupControlName}}</label>
<input type="text" formControlName="{{formGroupControlName}}">
</ng-container>