如何将动态添加的自定义组件注册为表单组中的表单控件
How to register a dynamically added custom component as a form control within a formgroup
我创建了一些自定义组件,每个组件都实现了 ControlValueAccessor
,以便它们可以作为 FormGroup
中的控件。通常,唯一剩下要做的就是将 formControlName
指令添加到 HTML.
中的每个自定义组件
但是,我在运行时使用 技术将这些组件附加到表单。
我的问题是我无法使用包含的 FormGroup
注册这些组件,因为我无法使用动态添加的控件声明 formControlName
指令(或任何指令,就此而言)。
还有其他人发现这怎么可能吗? 目前,我将每个控件包装在另一个组件中,以便我可以使用 formControlName
指令但这太丑陋而且劳动强度大。
下面是我已经实现为独立 Angular2 应用程序的精简示例,它显示了在启动时以编程方式添加的组件 (CustomComponent
) .为了将 CustomComponent
绑定到 FormGroup
,我不得不创建 CustomContainerComponent
,我希望避免这样做。
import {
Component, ReflectiveInjector, ViewContainerRef, Compiler, NgModule, ModuleWithComponentFactories,
OnInit, ViewChild, forwardRef
} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {NG_VALUE_ACCESSOR, ControlValueAccessor, FormGroup, FormBuilder, ReactiveFormsModule} from '@angular/forms';
export class AbstractValueAccessor<T> implements ControlValueAccessor {
_value: T;
get value(): T {
return this._value;
};
set value(v: T) {
if (v !== this._value) {
this._value = v;
this.onChange(v);
}
}
writeValue(value: T) {
this._value = value;
this.onChange(value);
}
onChange = (_) => {};
onTouched = () => {};
registerOnChange(fn: (_: any) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
}
export function MakeProvider(type: any) {
return {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => type),
multi: true
};
}
@Component({
selector: 'app-custom',
template: `<input type="text" [value]="value">`,
providers: [MakeProvider(CustomComponent)]
})
class CustomComponent extends AbstractValueAccessor<string> {
}
@Component({
selector: 'app-custom-container',
template: `<div [formGroup]="formGroup"><app-custom formControlName="comp"></app-custom></div>`
})
class CustomContainerComponent {
formGroup: FormGroup;
}
@NgModule({
imports: [BrowserModule, ReactiveFormsModule],
declarations: [CustomComponent, CustomContainerComponent]
})
class DynamicModule {
}
@Component({
selector: 'app-root',
template: `<h4>Dynamic Components</h4><br>
<form [formGroup]="formGroup">
<div #dynamicContentPlaceholder></div>
</form>`
})
export class AppComponent implements OnInit {
@ViewChild('dynamicContentPlaceholder', {read: ViewContainerRef})
public readonly vcRef: ViewContainerRef;
factory: ModuleWithComponentFactories<DynamicModule>;
formGroup: FormGroup;
constructor(private compiler: Compiler, private formBuilder: FormBuilder) {
}
ngOnInit() {
this.compiler.compileModuleAndAllComponentsAsync(DynamicModule)
.then((moduleWithComponentFactories: ModuleWithComponentFactories<DynamicModule>) => {
this.factory = moduleWithComponentFactories;
const compFactory = this.factory.componentFactories.find(x => x.selector === 'app-custom-container');
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
let cmp = this.vcRef.createComponent(compFactory, this.vcRef.length, injector, []);
(<CustomContainerComponent>cmp.instance).formGroup = this.formGroup;
});
this.formGroup = this.formBuilder.group({
'comp': ['hello world']
})
}
}
@NgModule({
imports: [BrowserModule, ReactiveFormsModule],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {
}
如果我没有误解你的需求...
奇怪的是,您将表单组附加到 AppComponent
内。相反,您可以动态创建完整的表单。为此,您需要实现两个功能:
- 从 JSON 配置
生成模板的一个
- 另一个从JSON配置
生成表单
然后简单地构建一个包含所有输入的大表单容器组件。我会推荐使用这里的服务:
@Injectable()
export class ModuleFactory {
public createComponent(jsonConfig: SomeInterface) {
@Component({
template: `
<div ngForm="form">${ /* and here generate the template containing all inputs */ }</div>
`
})
class FormContainerComponent {
public form: FormGroup = /* generate a form here */;
};
return FormContainerComponent;
}
public createModule(component: any) {
@NgModule({
imports: [
CommonModule,
ReactiveFormsModule
],
declarations: [ component ]
})
class MyModule {};
return MyModule;
}
}
这当然只是一个简化版本,但它适用于我的项目,不仅适用于普通表单,而且适用于嵌套表单组/数组/控件。
注入服务后,您可以使用这两个函数生成模块和组件。
我创建了一些自定义组件,每个组件都实现了 ControlValueAccessor
,以便它们可以作为 FormGroup
中的控件。通常,唯一剩下要做的就是将 formControlName
指令添加到 HTML.
但是,我在运行时使用
我的问题是我无法使用包含的 FormGroup
注册这些组件,因为我无法使用动态添加的控件声明 formControlName
指令(或任何指令,就此而言)。
还有其他人发现这怎么可能吗? 目前,我将每个控件包装在另一个组件中,以便我可以使用 formControlName
指令但这太丑陋而且劳动强度大。
下面是我已经实现为独立 Angular2 应用程序的精简示例,它显示了在启动时以编程方式添加的组件 (CustomComponent
) .为了将 CustomComponent
绑定到 FormGroup
,我不得不创建 CustomContainerComponent
,我希望避免这样做。
import {
Component, ReflectiveInjector, ViewContainerRef, Compiler, NgModule, ModuleWithComponentFactories,
OnInit, ViewChild, forwardRef
} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {NG_VALUE_ACCESSOR, ControlValueAccessor, FormGroup, FormBuilder, ReactiveFormsModule} from '@angular/forms';
export class AbstractValueAccessor<T> implements ControlValueAccessor {
_value: T;
get value(): T {
return this._value;
};
set value(v: T) {
if (v !== this._value) {
this._value = v;
this.onChange(v);
}
}
writeValue(value: T) {
this._value = value;
this.onChange(value);
}
onChange = (_) => {};
onTouched = () => {};
registerOnChange(fn: (_: any) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
}
export function MakeProvider(type: any) {
return {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => type),
multi: true
};
}
@Component({
selector: 'app-custom',
template: `<input type="text" [value]="value">`,
providers: [MakeProvider(CustomComponent)]
})
class CustomComponent extends AbstractValueAccessor<string> {
}
@Component({
selector: 'app-custom-container',
template: `<div [formGroup]="formGroup"><app-custom formControlName="comp"></app-custom></div>`
})
class CustomContainerComponent {
formGroup: FormGroup;
}
@NgModule({
imports: [BrowserModule, ReactiveFormsModule],
declarations: [CustomComponent, CustomContainerComponent]
})
class DynamicModule {
}
@Component({
selector: 'app-root',
template: `<h4>Dynamic Components</h4><br>
<form [formGroup]="formGroup">
<div #dynamicContentPlaceholder></div>
</form>`
})
export class AppComponent implements OnInit {
@ViewChild('dynamicContentPlaceholder', {read: ViewContainerRef})
public readonly vcRef: ViewContainerRef;
factory: ModuleWithComponentFactories<DynamicModule>;
formGroup: FormGroup;
constructor(private compiler: Compiler, private formBuilder: FormBuilder) {
}
ngOnInit() {
this.compiler.compileModuleAndAllComponentsAsync(DynamicModule)
.then((moduleWithComponentFactories: ModuleWithComponentFactories<DynamicModule>) => {
this.factory = moduleWithComponentFactories;
const compFactory = this.factory.componentFactories.find(x => x.selector === 'app-custom-container');
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
let cmp = this.vcRef.createComponent(compFactory, this.vcRef.length, injector, []);
(<CustomContainerComponent>cmp.instance).formGroup = this.formGroup;
});
this.formGroup = this.formBuilder.group({
'comp': ['hello world']
})
}
}
@NgModule({
imports: [BrowserModule, ReactiveFormsModule],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {
}
如果我没有误解你的需求...
奇怪的是,您将表单组附加到 AppComponent
内。相反,您可以动态创建完整的表单。为此,您需要实现两个功能:
- 从 JSON 配置 生成模板的一个
- 另一个从JSON配置 生成表单
然后简单地构建一个包含所有输入的大表单容器组件。我会推荐使用这里的服务:
@Injectable()
export class ModuleFactory {
public createComponent(jsonConfig: SomeInterface) {
@Component({
template: `
<div ngForm="form">${ /* and here generate the template containing all inputs */ }</div>
`
})
class FormContainerComponent {
public form: FormGroup = /* generate a form here */;
};
return FormContainerComponent;
}
public createModule(component: any) {
@NgModule({
imports: [
CommonModule,
ReactiveFormsModule
],
declarations: [ component ]
})
class MyModule {};
return MyModule;
}
}
这当然只是一个简化版本,但它适用于我的项目,不仅适用于普通表单,而且适用于嵌套表单组/数组/控件。
注入服务后,您可以使用这两个函数生成模块和组件。