【问题标题】:Dynamic component loader - shared service for the resolving components动态组件加载器 - 解析组件的共享服务
【发布时间】:2017-12-11 13:58:11
【问题描述】:

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

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

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

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

知道如何创建一个好的服务来使用这个代码吗?

【问题讨论】:

  • 这是为你的模态组件的子组件,对吗?
  • @OsmanCea 是的,你说得对。
  • 我之前做过类似的事情,但在我尝试发布答案之前,我想知道一些事情。你是如何设计你的模态的?您想在整个应用程序中拥有一个模态组件实例吗?或者您可能想跨越多个实例,然后动态创建相应模态的内容?
  • @OsmanCea 我的应用程序中有一个模态组件实例。
  • 好的,谢谢,这就是我需要知道的。我将在接下来的一两个小时内发布答案。

标签: angular typescript angular2-services dynamic-loading


【解决方案1】:

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

所以在这里你将保留所有可能的动态组件,让我们将此文件称为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属性中

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

到目前为止,您应该都知道这些。

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

因此,您不必像现在这样在方法中硬编码组件,而是必须从 dynamicComponents 数组中提取它。

由于 Javascript 类是函数的糖语法,所有非匿名函数都有一个 name 属性,这样您就可以将函数提供的名称参数与 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 类的定义:

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

创建这个帮助类的原因,是因为resolveComponentFactory 期待一个带有Type&lt;T&gt; 的组件参数,而dynamicComponents.find() 的结果是一个联合类型,所以如果我们不希望打字稿编译器抱怨,我们应该修补这个类。

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

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 的外观或处理修补输入值的方式或作为处理程序传递给输出的内容,但这是动态创建不同组件的基本基础.


当然我也设置了demo。希望对您有所帮助。

12/14/07 编辑:

显然访问Function 对象的name 属性在生产环境中并不能真正起作用(你会得到一个没有找到组件工厂的错误),因为在丑化你的代码之后,函数的名称会被破坏并获胜不匹配。 angular repo 上有一个问题评论,解释了一些解决此问题的方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-25
    • 1970-01-01
    • 2022-01-22
    • 1970-01-01
    • 2017-01-04
    • 2017-04-15
    • 2021-09-24
    相关资源
    最近更新 更多