首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >角4: ComponentFactoryResolver -架构建议

角4: ComponentFactoryResolver -架构建议
EN

Stack Overflow用户
提问于 2018-04-02 08:08:23
回答 1查看 371关注 0票数 2

我需要一些关于ComponentFactoryResolver和架构的建议。我的代码中有n个面板(后端提供面板的数量),在这些面板中,我有动态字段(后端也提供了这个数字)。例如,每个面板应该在开始时有4个输入字段。根据用户请求,可以删除或添加字段。我试着用ComponentFactoryResolver来解决这个问题,我坚持了一点。

首先,我尝试有两个嵌套循环,一个用于面板,另一个用于字段--不工作,下面的代码永远不会呈现在页面上。看起来ng模板没有计算出我的动态字段--或者我遗漏了什么。

代码语言:javascript
复制
 <div #container *ngFor="let i of [1,2,3]"></div>

第二,我已经将代码从TypeScript移到了AfterViewInit,现在我正在使用AfterViewInit循环,并且在我的页面上有动态字段-但是现在我遇到了一个问题,所有字段都显示在第一个面板中,每个面板应该有4个字段.

此外,用于添加和移除字段的按钮只适用于具体面板。例如:如果我单击第二个面板中的第二个add按钮,我就会在第二个面板中显示add。在我的例子中,这只适用于第一个面板。

  1. 知道如何正确地解决这个问题吗?
  2. 我是否正确地使用ComponentFactoryResolver?
  3. 为什么使用ngFor循环的第一个解决方案不能工作?
  4. 如何使用ComponentFactoryResolver和ngModel?
  5. 这有可能吗,还是我需要彻底改变我的策略?

我不想使用某些ngIf语句并定义一些字段。我想学习解决这类问题的动态和通用的方法。

我做了一个柱塞演示:https://plnkr.co/edit/FjCbThpmBmDcgixTpXOy?p=preview

很抱歉有这么长的邮筒。我希望我已经很好地解释了这个问题。如有任何建议,将不胜感激。

EN

回答 1

Stack Overflow用户

发布于 2018-04-02 08:47:29

我有一个类似的要求,并使用了ComponentFactoryResolver。但是,我在ng-template上放置了一个包装器,如下所示:

代码语言:javascript
复制
@Component({
    selector: 'tn-dynamic',
    template: `<ng-template #container></ng-template>`,
    providers: [SubscriptionManagerService],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class DynamicComponent<T>
    extends DynamicComponentBase<T, T, ComponentData>
    implements OnChanges, OnDestroy {

    @Input()
    set componentData(data: ComponentData) {
        if (!data) {
            return;
        }

        try {
            let type: Type<T> = getComponentType(data.type);

            this._factory = this.resolver.resolveComponentFactory(type);
            let injector = Injector.create([], this.vcRef.parentInjector);
            this._factoryComponent = this.container.createComponent(this._factory, 0, injector);
            this._currentComponent = this._factoryComponent.instance;
    this._factoryComponent.location.nativeElement.classList.add('tn-dynamic-child');

        } catch (er) {
            console.error(`The type ${data.type.library}
.${data.type.key} has not been registered. Check entryComponents?`);
            throw er;
        }
    }

    // Handle ngOnChanges, ngOnDestroy
}

然后我会把我的循环放在我的组件<tn-dynamic [componentData]="myComponentData">

您的componentData包含控件的类型,因此您将有另一个服务,它将根据请求的类型返回正确的类型。

当您开始使用解析器时,您的输入/输出不会被分配。所以你得自己处理。

代码语言:javascript
复制
ngOnChanges(changes: SimpleChanges) {
    let propertyWatch = this.getPropertyWatch();
    for (let change in changes) {
        if (change === propertyWatch) {
            let data = this.getComponentData(changes[change].currentValue);
            if (data) {
                if (data.inputs) {
                    this.assignInputs(data.inputs);
                }

                if (data.outputs) {
                    this.assignOutputs(data.outputs);
                }

                if (this.implementsOnChanges()) {
                    let dynamiChanges = DynamicChanges.create(data);
                    if (dynamiChanges) {
                        (<OnChanges><any>this._currentComponent).ngOnChanges(dynamiChanges);
                    }
                }
            }
        }
    }
}

private unassignVariables() {
    if (this.factory && this.factory.inputs) {
        for (let d of this.factory.inputs) {
            this._currentComponent[d.propName] = null;
        }
    }
}

protected assignInputs(inputs: ComponentInput) {
    for (let key in inputs) {
        if (inputs[key] !== undefined) {
            this._currentComponent[key] = inputs[key];
        }
    }
}

private assignOutputs(outputs: ComponentOutput) {
    for (let key in outputs) {
        if (outputs[key] !== undefined) {
            let eventEmitter: EventEmitter<any> = this._currentComponent[key];
            let subscription = eventEmitter.subscribe(m => outputs[key](m));
            this.sm.add(subscription);
        }
    }
}

然后,我发现用formControl而不是ngModel来处理表单输入更好。特别是当涉及到处理验证器时。如果您继续使用ngModel,您将无法轻松地添加/删除验证器。但是,创建一个动态组件,并将[formControl]附加到ComponentFactoryResolver生成的控件上似乎是不可能的。所以我不得不动态编译模板。因此,我用另一个服务创建了一个控件,如下所示:

代码语言:javascript
复制
const COMPONENT_NAME = 'component';

@Injectable()
export class RuntimeComponent {
    constructor(
        private compiler: Compiler,
        @Optional() @Inject(DEFAULT_IMPORTS_TOKEN)
        protected defaultImports: DefaultImports
    ) {
    }

    protected createNewComponent(tmpl: string, args: any[]): Type<any> {
        @Component({
            selector: 'tn-runtime-component',
            template: tmpl,
        })
        class CustomDynamicComponent<T> implements AfterViewInit, DynamicComponentData<T> {
            @ViewChild(COMPONENT_NAME)
            component: T;

            constructor(
                private cd: ChangeDetectorRef
            ) { }

            ngAfterViewInit() {
                this.cd.detectChanges();
            }
        }

        Object.defineProperty(CustomDynamicComponent.prototype, 'args', {
            get: function () {
                return args;
            }
        });

        // a component for this particular template
        return CustomDynamicComponent;
    }

    protected createComponentModule(componentType: any) {
        let imports = [
            CommonModule,
            FormsModule,
            ReactiveFormsModule
        ];

        if (this.defaultImports && this.defaultImports.imports) {
            imports.push(...this.defaultImports.imports);
        }

        @NgModule({
            imports: imports,
            declarations: [
                componentType
            ],
        })
        class RuntimeComponentModule {
        }
        // a module for just this Type
        return RuntimeComponentModule;
    }

    public createComponentFactoryFromStringSync(template: string, attributeValues?: any[]) {
        let type = this.createNewComponent(template, attributeValues);
        let module = this.createComponentModule(type);

        let mwcf = this.compiler.compileModuleAndAllComponentsSync(module);
        return mwcf.componentFactories.find(m => m.componentType === type);
    }

    public createComponentFactoryFromMetadataSync(selector: string, attributes: { [attribute: string]: any }) {
        let keys = Object.keys(attributes);
        let attributeValues = Object.values(attributes);
        let attributeString = keys.map((attribute, index) => {
            let isValueAFunctionAsString = typeof attributeValues[index] === 'function' ? '($event)' : '';
            return `${attribute}="args[${index}]${isValueAFunctionAsString}"`;
        }).join(' ');

        let template = `<${selector} #${COMPONENT_NAME} ${attributeString}></${selector}>`;
        return this.createComponentFactoryFromStringSync(template, attributeValues);
    }
}

我的代码并不完美,这就是为什么我只给你重要的部分。你必须利用这个想法,让它按你的方式运作。有一天我应该写一篇关于这件事的博文:)

我看了一下您的柱塞,在使用ngFor时,您没有正确地使用#名称。如果您的循环正常工作,您将无法在TypeScript中正确地获得它。

另外,您不能在ng模板上执行*ngFor=""。所以你的循环不起作用了。请参阅https://toddmotto.com/angular-ngfor-template-element

祝好运!

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/49607603

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档