简体   繁体   English

Angular 4:ComponentFactoryResolver-体系结构建议

[英]Angular 4: ComponentFactoryResolver - architecture advice

I need some advice about ComponentFactoryResolver and architecture. 我需要有关ComponentFactoryResolver和体系结构的一些建议。 I have n panels in my code (the backend is providing the number of panels) and in these panels, I have dynamic fields (the number is also provided by the backend). 我的代码中有n个面板(后端提供了面板的数量),在这些面板中,我具有动态字段(后端也提供了数量)。 For example, every panel should have 4 input filed at the beginning. 例如,每个面板的开头应有4个输入字段。 The fields can be removed or add, depending on user request. 可以根据用户要求删除或添加字段。 I have tried to solve this with the ComponentFactoryResolver, I have stuck a little bit. 我试着用ComponentFactoryResolver解决这个问题,我有点卡住了。

First I have tried to have two nested loop, one for panels and one for fields - not working, the code below is never rendering on the page. 首先,我尝试了两个嵌套循环,一个用于面板,一个用于字段-不能正常工作,下面的代码永远不会在页面上呈现。 It seems like ng template is not figuring out my dynamic fields - or I am missing something. ng模板似乎无法弄清楚我的动态字段-或我缺少了一些东西。

 <div #container *ngFor="let i of [1,2,3]"></div>

Second, I have moved the code from HTML to TypeScript, now I am using AfterViewInit cycle and I have managed to have dynamic filed on my page - but now I have a problem that all fields are shown in the first panel and there should be 4 fields per panel... 其次,我已经将代码从HTML移到TypeScript,现在我使用AfterViewInit循环,并且设法在页面上动态保存文件-但是现在我遇到了一个问题,所有字段都显示在第一个面板中,应该有4个每个面板的字段...

Also, buttons for adding and removing fields should work only for the concrete panel. 另外,用于添加和删除字段的按钮应仅适用于混凝土面板。 For example: if I click on the second add button in the second panel, I show add filed in the second panel. 例如:如果我单击第二个面板中的第二个添加按钮,则会在第二个面板中显示添加字段。 In my case, this is only working for the first panel. 就我而言,这仅适用于第一个面板。

  1. Any idea how to solve this properly, as angular way? 任何想法如何以正确的方式解决这个问题?
  2. Do I use ComponentFactoryResolver properly? 我是否可以正确使用ComponentFactoryResolver?
  3. Why does the first solution with ngFor loop not work? 为什么使用ngFor循环的第一个解决方案不起作用?
  4. How to use ComponentFactoryResolver and ngModel? 如何使用ComponentFactoryResolver和ngModel?
  5. Is this even possible, or I need to change my strategy completely? 这是否有可能,或者我需要彻底改变策略?

I don't want to use some ngIf statement and define some number of fields. 我不想使用一些ngIf语句并定义一些字段。 I want to learn the dynamic and generic way for solving this kind of issues. 我想学习解决此类问题的动态通用方法。

I have made a plunker demo: https://plnkr.co/edit/FjCbThpmBmDcgixTpXOy?p=preview 我做了一个小小的演示: https ://plnkr.co/edit/FjCbThpmBmDcgixTpXOy ? p = preview

Sorry about the long post. 抱歉,帖子很长。 I hope that I have explained the issue very well. 我希望我已经很好地解释了这个问题。 Any advice would be greatly appreciated. 任何建议将不胜感激。

I have a similar requirement and used the ComponentFactoryResolver . 我有类似的要求,并使用了ComponentFactoryResolver However, I have put a wrapper around the ng-template like this: 但是,我在ng-template周围放置了一个包装,如下所示:

@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
}

Then I would put my loop around my component <tn-dynamic [componentData]="myComponentData"> 然后,将循环放在组件<tn-dynamic [componentData]="myComponentData">

Your componentData contains the type of control, so you would have another service that would return the right type based on the requested type. 您的componentData包含控件的类型,因此您将拥有另一个服务,该服务将根据请求的类型返回正确的类型。

When you start using resolver as such, your inputs/outputs are not assigned. 当您这样开始使用解析器时,不会分配您的输入/输出。 So you have to handle that yourself. 因此,您必须自己处理。

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);
        }
    }
}

Then, I found that it was better to handle my form inputs with formControl rather than ngModel . 然后,我发现最好使用formControl而不是ngModel处理表单输入。 Especially when it comes to handle the validators. 特别是在处理验证器时。 If you keep with ngModel , you will not be able to add/remove validators easily. 如果您使用ngModel ,则将无法轻松添加/删除验证器。 However, creating a dynamic component WITH a [formControl] attached to the control generated with the ComponentFactoryResolver seems to be impossible. 但是,创建带有[formControl]附加到由ComponentFactoryResolver生成的控件的动态ComponentFactoryResolver似乎是不可能的。 So I had to compile template on the fly. 因此,我不得不即时编译模板。 So I create a control with another service that looks like this: 因此,我使用另一个服务创建了一个控件,如下所示:

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);
    }
}

My code is not perfect, and this is why I give you only the important parts. 我的代码并不完美,这就是为什么我只给您重要的部分。 You have to play with the idea and make it work your way. 您必须遵循这个想法,并使它按自己的方式工作。 I should write a blog post about this one day :) 我应该写一篇关于这一天的博客文章:)

I looked at your plunker and you are NOT using the #name properly when using a ngFor. 我看着你的小矮人,当你使用ngFor时,你没有正确使用#name。 You won't get it properly in TypeScript if your loop is working. 如果您的循环正常工作,您将无法在TypeScript中正确获取它。

Also, you can't do *ngFor="" on an ng-template. 另外,您不能在ng-template上执行*ngFor="" So your loop is just not working. 因此,您的循环无法正常工作。 See https://toddmotto.com/angular-ngfor-template-element 参见https://toddmotto.com/angular-ngfor-template-element

Good luck! 祝好运!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Angular 5-ComponentFactoryResolver和属性 - Angular 5 - ComponentFactoryResolver and properties Angular @Attribute 装饰器和 ComponentFactoryResolver - Angular @Attribute decorator and ComponentFactoryResolver Angular 8-体系结构/路由最佳实践(建议) - Angular 8 - Architecture/Routing Best Practice (advice) 替换已弃用的 Angular ComponentFactoryResolver, ComponentFactory - Replace deprecated Angular ComponentFactoryResolver, ComponentFactory Angular 6 custom lib没有ComponentFactoryResolver的提供程序 - Angular 6 custom lib No provider for ComponentFactoryResolver 如何在服务中替换 Angular ComponentFactoryResolver + ComponentFactory? - How to replace Angular ComponentFactoryResolver + ComponentFactory in a service? Angular ViewChild和ComponentFactoryResolver-使状态保持活动状态 - Angular ViewChild and ComponentFactoryResolver - keep state alive Angular 使用 ComponentFactoryResolver 创建多个组件 - Angular Create multiple components using ComponentFactoryResolver 在 Angular7 应用程序中使用 ComponentFactoryResolver 是个好主意吗? - Is it good idea to use ComponentFactoryResolver in Angular7 application? 使用 ComponentFactoryResolver 导入 Angular 动态组件:传入的类型不是 ComponentType - Angular dynamic component import with ComponentFactoryResolver: Type passed in is not ComponentType
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM