Angular 2:如何跨动态创建的组件链接表单元素? [英] Angular 2: How to link form elements across a dynamically created components?

查看:21
本文介绍了Angular 2:如何跨动态创建的组件链接表单元素?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一组动态创建的组件中的表单字段.父组件拥有 form 标签.但是,没有任何表单字段被添加到 Form.我正在使用 ComponentFactoryResolver 来创建组件:

@Component({选择器:'字段集容器',templateUrl: './fieldset-container.component.html',styleUrls: ['./fieldset-container.component.scss'],条目组件:ALL_FIELD_SETS,})导出类 FieldsetContainerComponent{fieldsetComponent : ComponentRef= 空;@Input() 表单组:表单组;@ViewChild('fieldSetContainer', {读取:ViewContainerRef})fieldsetContainer : ViewContainerRef;@Output() onComponentCreation = new EventEmitter>();构造函数(私有解析器:ComponentFactoryResolver){}@Input() set fieldset( fieldset : {component : any, resolve : any }) {如果(!fieldset)返回;//抱歉不对//输入需要采用以下格式才能正确解析让 inputProviders = Object.keys(fieldset.resolve).map((resolveName) => {return {provide: resolveName, useValue: fieldset.resolve[resolveName]};});让resolvedInputs = ReflectiveInjector.resolve(inputProviders);//我们从我们想要传递的数据中创建一个注入器和这个组件注入器让注入器 = ReflectiveInjector.fromResolvedProviders(resolvedInputs, this.fieldsetContainer.parentInjector);//我们从想要创建的组件中创建一个工厂让工厂 = this.resolver.resolveComponentFactory(findComponentForFieldset(fieldset.component));//我们使用工厂和注入器创建组件让组件:ComponentRef= factory.create(injector);//我们将组件插入到dom容器中this.fieldsetContainer.insert(component.hostView);//销毁之前创建的组件如果(this.fieldsetComponent){this.fieldsetComponent.destroy();}this.fieldsetComponent = 组件;this.onComponentCreation.emit( this.fieldsetComponent );}}

模板:

</div>

动态组件的使用:

<div *ngFor="let fieldset of page?.fieldsets"><fieldset-container [fieldset]="{ component: fieldset, resolve: {} }" (onComponentCreation)="onComponentCreation($event)" [formGroup]="omaForm.form"></fieldset-container>

</表单>

我怀疑这与注入器未正确连接有关,但据我所知,它已链接到父级.我在 NgModel 中设置了一个断点,它为 parent 传递了一个空值,这是问题所在.我将其追溯到看起来已编译的东西,它只是硬编码空值.所以我不确定它是如何用硬编码空值创建的.

关于如何解决这个问题有什么想法吗?

解决方案

好吧,事实证明它与该组件的动态特性无关.我删除了它并内联定义了我的所有组件,但它仍然存在问题.问题在于,Angular 开箱即用地不支持嵌套在表单标签中的组件内的表单控件.一旦你在一个组件中嵌套了一个表单控件,它就再也看不到 NgForm 了,这太疯狂了.

在网上阅读解决方案并看到没有人有好的解决方案后,我设计了 2 个自己的指令,将表单注册到 NgForm 上的 DI 容器中,然后使用 DI 层次结构我可以将其注入另一个指令中将执行下面的注册.

父组件模板:

<表单嵌套><我的组件.../></表单>

子组件模板:

<input name="street" [(ngModel)]="address.street" 需要嵌套/><input name="city" [(ngModel)]="address.city" required nest/><input name="state" [(ngModel)]="address.state" required nest/><input name="zip" [(ngModel)]="address.zip" required nest/>

一旦我有了这个,我就可以带回我的动态组件,它工作得很好.到达那里真的很难.

它非常优雅和简单,不需要我像网络秀上的许多建议一样将表单实例向下传递到层.注册一个表单控件无论是删除1层还是999层的工作都是一样的.

恕我直言,NgForm 和 NgModel 应该为我们开箱即用,但它们不会导致复杂的架构设计以完成中等高级的表单.

I have a set of form fields that are in a dynamically created component. The parent Component owns the form tag. However, none of the form fields are being added to the Form. I'm using the ComponentFactoryResolver to create the component:

@Component({
selector: 'fieldset-container',
templateUrl: './fieldset-container.component.html',
styleUrls: ['./fieldset-container.component.scss'],
entryComponents: ALL_FIELD_SETS,
})
export class FieldsetContainerComponent<C> {

fieldsetComponent : ComponentRef<any> = null;

@Input() formGroup : FormGroup;

@ViewChild('fieldSetContainer', {read: ViewContainerRef})
fieldsetContainer : ViewContainerRef;

@Output() onComponentCreation = new EventEmitter<ComponentRef<any>>();

constructor(private resolver : ComponentFactoryResolver) {
}

@Input() set fieldset( fieldset : {component : any, resolve : any }) {
    if( !fieldset ) return; // sorry not right

    // Inputs need to be in the following format to be resolved properly
    let inputProviders = Object.keys(fieldset.resolve).map((resolveName) => {return {provide: resolveName, useValue: fieldset.resolve[resolveName]};});
    let resolvedInputs = ReflectiveInjector.resolve(inputProviders);

    // We create an injector out of the data we want to pass down and this components injector
    let injector = ReflectiveInjector.fromResolvedProviders(resolvedInputs, this.fieldsetContainer.parentInjector);


    // We create a factory out of the component we want to create
    let factory = this.resolver.resolveComponentFactory(findComponentForFieldset(fieldset.component));

    // We create the component using the factory and the injector
    let component : ComponentRef<any> = factory.create(injector);

    // We insert the component into the dom container
    this.fieldsetContainer.insert(component.hostView);

    // Destroy the previously created component
    if (this.fieldsetComponent) {
        this.fieldsetComponent.destroy();
    }

    this.fieldsetComponent = component;
    this.onComponentCreation.emit( this.fieldsetComponent );
}
}

The template:

<div #fieldSetContainer [formGroup]="formGroup"></div>

The usage of the dynamic component:

<form class="form" #omaForm="ngForm">
    <div *ngFor="let fieldset of page?.fieldsets">
        <fieldset-container [fieldset]="{ component: fieldset, resolve: {} }" (onComponentCreation)="onComponentCreation($event)" [formGroup]="omaForm.form"></fieldset-container>
    </div>
</form>

I suspect it has something to do with the injector not being hooked up correctly, but from what I can tell it is chained to the parent. I've set a breakpoint in NgModel and it is passed a null for parent which is the problem. I traced that back up into something that looks compiled and it was just hard coding a null. So I'm not sure how that was created with hard coded nulls in there.

Any ideas on how to fix this?

解决方案

Ok it turns out it has nothing to do with the dynamic nature of this component. I removed it and defined all of my components inline and it still had the problem. The issue was that having form controls inside a Component that were nested within a form tag is just not supported by Angular out of the box. Once you nest a form control in a component it can't see the NgForm anymore which is crazy.

After reading solutions on the web and seeing that no one had a good solution I designed 2 of my own directives that registered the Form into the DI container up at the NgForm, then using DI hierarchy I could inject that into another Directive that would perform the registration below.

Parent Component Template:

<form nested>
    <my-component .../>
</form>

Child Component Template:

<div>
   <input name="street" [(ngModel)]="address.street" required nest/>
   <input name="city" [(ngModel)]="address.city" required nest/>
   <input name="state" [(ngModel)]="address.state" required nest/>
   <input name="zip" [(ngModel)]="address.zip" required nest/>
</div>

Once I had this in place then I could bring back my dynamic component and it worked perfectly. It was just really hard to get there.

It's really elegant and simple and doesn't require me to pass the form instance down through the layers like so many suggestions on the web show. And the work to register a form control whether it's 1 layer or 999 layers removed is the same.

IMHO NgForm and NgModel should just do this out of the box for us, but they don't which leads to complicated architecture design to accomplish moderately advanced forms.

这篇关于Angular 2:如何跨动态创建的组件链接表单元素?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆