动态组件加载器 - 解析组件的共享服务 [英] Dynamic component loader - shared service for the resolving components

查看:21
本文介绍了动态组件加载器 - 解析组件的共享服务的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我必须使用 ComponentFactoryResolver 来动态添加组件.对于我的示例,我有一个模式窗口,我在按下视图上的某个按钮后动态加载了该窗口.此操作的代码如下:

I have to use ComponentFactoryResolver to add components dynamically. For my example, I have a modal window which I dynamically loaded after pressing some button on the view. And code for this action like this:

let componentFactory = this.componentFactoryResolver.resolveComponentFactory(XYZComponent);
let viewContainerRef = this.containerHost.viewContainerRef;
viewContainerRef.clear();
let componentRef = viewContainerRef.createComponent(componentFactory);

let _XYZComponent = componentRef.instance as XYZComponent;

_XYZComponent.close.subscribe(() => {
    componentRef.destroy();
});

每次我想使用这个模态窗口时,我都需要放类似的代码,只是组件名称不同.

Every time when I would like to use this modal window I need to put the similar code, only with the different component name.

我想为此创建一个共享服务,但我找不到适合我的情况的好的解决方案,我的意思是动态加载的组件.

I would like to create a shared service for this but I can't find a good solution for my case I mean dynamically loaded components.

知道如何创建使用此代码的良好服务吗?

Any idea how to create a good service to the use this code?

推荐答案

由于您将动态加载组件,因此您必须在某处注册这些组件,也就是在模态组件的 entryComponents 内装饰器.此外,由于这些组件是 typescript class es,您需要从您调用模态的任何组件导入它们.为了在一个地方处理这个问题,我们将把这些导入到一个文件中,然后将它们导出到一个数组中.

Since you will be loading components dynamically, you have to register these components somewhere, and that is inside the entryComponents of your modal component decorator. Also, since these components are typescript classes, you need to import them from whatever component you're calling your modal. In order to handle this in just one place, we're going to import these into a single file and then export them inside an array.

所以在这里你将保留所有可能的动态组件,让我们调用这个文件 dynamic-components.ts:

So here you will keep all the possible dynamic components, lets call this file dynamic-components.ts:

import { FooComponent } from './path/to/foo';
import { BarComponent } from './path/to/bar';
import { BazComponent } from './path/to/baz';

// export all dynamic components
export const dynamicComponents = [
  FooComponent, BarComponent, BazComponent
]

然后在你的模态组件中,你可以将这些组件分散到entryComponents属性中

Then in your modal component, you can spread these components into the entryComponents property

import { dynamicComponents } from './path/to/dynamic-components';
@Component({
  //...
  entryComponents: [ ...dynamicComponents ]
})
export class ModalComponent {}

到目前为止,您应该都知道了.

So far this should be all known to you.

现在,在你的模态组件中,你可以创建一个方法来渲染一个组件,将组件名称和一些元数据作为参数来动态处理道具,道具我的意思是 @Input()@Output() 装饰属性.这将使您的模态更加灵活,因为您将能够渲染具有不同输入和输出的组件.

Now, inside your modal component, you can create a method that will render a component taking as parameter the component name and also some metadata to handle props dynamically, by props I mean @Input() and @Output() decorated properties. This will make your modal more flexible, since you will be able to render components with different inputs and outputs.

因此,您不必像现在那样在方法中对组件进行硬编码,而是必须从 dynamicComponents 数组中提取它.

So instead of hardcoding the component in the method like you're doing now, you'll have to extract it from the dynamicComponents array.

由于 Javascript 类是函数的糖语法,所有非匿名函数都有一个 name 属性,这样您就可以将函数提供的 name 参数与 dynamicComponents.

Since Javascript classes are sugar syntax for functions, all non-anonymous functions have a name property, this way you can match the name parameter provided by your function with the name of the components in dynamicComponents.

export class ModalComponent { 
  //...
  createComponent(name: string, metadata: any) {
    const viewContainerRef = this.entryContainer.viewContainerRef;
    const cmpClass = dynamicComponents.find(cmp => cmp.name === name);
    const cmpToCreate = new DynamicComponent(cmpClass, metadata);

    const componentFactory = this.cmpFactoryResolver.resolveComponentFactory(cmpToCreate.component)

    viewContainerRef.clear();

    const cmpRef = viewContainerRef.createComponent(componentFactory);


    // patch input values ...
    for ( let input in metadata.inputs ) {
      if ( metadata.inputs.hasOwnProperty(input) ) {
        cmpRef.instance[input] = metadata.inputs[input];
      }
    }


    // and subscribe to outputs
    for ( let output in metadata.outputs ) {
      if ( metadata.outputs.hasOwnProperty(output) ) {
        console.log('hasOuput', metadata.outputs[output]);
        cmpRef.instance[output].subscribe(metadata.outputs[output]);
      }
    }
  }
}

有几件事要提.下面是 DynamicComponent 类的定义:

A couple things to mention. Here's the definition for the DynamicComponent class:

export class DynamicComponent {
  constructor(public component: Type<any>, data: any) {}
}

创建这个辅助类的原因,是因为 resolveComponentFactory 需要一个带有 Type 的组件参数,以及 dynamicComponents.find() 是联合类型,所以如果我们不想打字稿编译器抱怨,我们应该patch这个类.

The reason for creating this helper class, is because resolveComponentFactory is expecting a component parameter with Type<T>, and the result of dynamicComponents.find() is a union type, so if we don't want the typescript compiler to complain, we should patch this class.

除了 metadata 参数之外,其余的函数几乎就是你所拥有的.现在,如果您在模态中实例化的组件具有输入和输出,除非您专门设计这些组件以满足某些标准,否则可能具有不同的输入和输出.所以这就是元数据参数,只是一个具有输入和输出的对象.我想当您实际调用该方法时会更清楚,如下所示:

The rest of the function is pretty much what you have, except for the metadata parameter. Now, if the components you are instantiating in your modal have inputs and outputs, unless you design these components specifically to meet some criteria, might have different inputs and outputs. So that's what the metada parameter is, just an object with inputs and outputs. I guess it's clearer when you actually call the method, like this:

export class SomeComponentThatRendersTheModal() {
  renderFooComponent() {
    // I don't know how you call your modal, so I'll just assume there's a modal service or whatever
    this.modalService.openModal();
    this.modalService.createComponent(
      'FooComponent', { 
        inputs  : { fooInputTest  : 'kitten' },
        outputs : { fooOutputTest : handleOutput } 
      }
    );
  }

  // You can pass this method as the subscription `next` handler
  handleOutput(emittedEvent) {
    // ...
  }
}

其中 FooComponent 是这样的:

@Component({
  selector: 'foo',
  template: `
    <h1>Foo Component, here's the input: " {{ fooInputTest }} "</h1>
    <button (click)="fooOutputTest.emit('Greetings from foo')">Foo output test</button>
  `
})
export class FooComponent {
  @Input()
  fooInputTest: any;

  @Output()
  fooOutputTest: EventEmitter<any> = new EventEmitter<any>();
}

现在,您当然可以更改 metadata 的外观,或者您如何处理修补输入值或作为处理程序传递给输出的内容,但这是您如何可以的基本基础动态创建不同的组件.

Now, of course you can change the way the metadata looks or how you handle patching the input values or what you pass as handlers to the outputs, but this is the basic foundation of how you can create a different components dynamically.

当然,我也设置了一个演示.希望有帮助.

Of course I've set up a demo too. Hope it helps.

12/14/07

显然访问 Function 对象的 name 属性并不能真正用于生产(你会得到一个 no component factory found 错误),因为在丑化你的代码,函数的名称被破坏并且不会匹配.angular repo 上有一个问题评论,解释了一些解决方法这个问题.

Apparently accessing the name property of a Function object doesn't really work for production (you'll get a no component factory found error), since after uglyfying your code, the names of function get mangled and won't match. There's an issue comment on the angular repo explaining some workarounds for this problem.

这篇关于动态组件加载器 - 解析组件的共享服务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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