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

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

问题描述

我必须使用 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内部的某个位置注册这些组件.另外,由于这些组件是打字稿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<T>的组件参数,并且dynamicComponents.find()的结果是联合类型,因此如果我们不希望打字稿编译器抱怨,我们应该修补此类.

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参数外,其余功能几乎就是您所拥有的.现在,如果要在模态中实例化的组件具有输入和输出,除非您专门设计这些组件以满足某些条件,否则它们可能具有不同的输入和输出.这就是metada参数,只是一个具有输入和输出的对象.我想当您实际调用该方法时,这样会更清楚:

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.

07/12/14修改:

显然,访问Function对象的name属性实际上不适用于生产(您将看到没有找到组件的错误),因为在丑陋了您的代码之后,函数的名称被弄乱了并赢得了不匹配. 角度回购上有一个问题评论,解释了一些解决方法这个问题.

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天全站免登陆