【问题标题】:Adding a Component to a TemplateRef using a structural Directive使用结构指令将组件添加到 TemplateRef
【发布时间】:2017-10-12 04:08:12
【问题描述】:

我们正在构建一个 Angular 4 组件库,其中一个组件是 Busy 组件。该组件的目的是允许开发人员在包含微调器图形的任何给定 HTML 元素上创建叠加层。

<div *xuiBusy="isBusy">...</div>

isBusy 的值为真时,我们希望附加到div 的内部内容,以便我们可以在内容之上呈现覆盖元素。

我们已经能够将该组件附加到ViewContainerRef,但是这会将busy元素作为div的兄弟元素插入,而不是根据需要插入div

    ngOnInit(): void {
      const compFactory = this._componentFactory.resolveComponentFactory(XuiBusyComponent);
      const comp = this._viewContainer.createComponent(compFactory);

消费者做什么:

<div *xuiBusy="isBusy">
  <span>This is the content</span>
</div>

isBusy 设置为 true 时,我们希望将市场更改为如下所示。请注意,&lt;spinner&gt; 已添加到 div 元素中。

<div *xuiBusy="isBusy">
  <span>This is the content</span>
  <spinner>Please wait...</spinner> <-- inserted by directive
</div>

感谢任何建议!

【问题讨论】:

  • 如果是这样,你为什么不用
    ...
  • 好问题。我们希望允许开发者消费者不必为他们的标记提供特殊结构来使组件工作。所以在最简单的形式中,我们希望允许他们用这个属性来装饰他们的元素,其余的由指令处理。
  • 那么你不应该使用div。您可以使用 。输出将是模板中的内容。没有 HTML 标签元素依赖。 angular.io/docs/ts/latest/api/common/index/…
  • div 是消费者开发者使用的,只是一个例子。它也可以是形式、文章、正文等。这个想法是添加繁忙元素的复杂性由指令处理。这在 ng1 中很容易实现,因为我们可以在链接阶段追加/插入。

标签: angular angular2-directives


【解决方案1】:

演示

我已经设置了a demo on StackBlitz。为简洁起见,Spinner 组件、Busy 指令和消费者都在app.component.ts 中。

设置

结构指令需要注入以下内容:

  • TemplateRef,对结构指令所在模板的引用(以脱糖语法);
  • ViewContainerRef,对可以在结构指令封装的视图内呈现的视图容器的引用;
  • ComponentFactoryResolver,一个知道如何从代码中动态创建组件实例的类。

Angular 中的注入是通过构造函数完成的。

constructor(private templateRef: TemplateRef<void>,
            private vcr: ViewContainerRef,
            private cfr: ComponentFactoryResolver) { }

传递布尔值

我们需要一个输入来从外部传递数据。为了使语法漂亮和明显(特别是当指令需要单个输入时,例如在这种情况下),我们可以将输入命名为与指令的选择器完全相同。

要重命名输入,我们可以将bindingPropertyName 传递给Input 装饰器。

@Input('xuiBusy') isBusy: boolean;

创建消费者的内容

消费者可以使用ViewContainerRef类上定义的createEmbeddedView方法动态创建的内容。第一个参数是唯一的强制参数,它接受插入视图所基于的模板引用:这是我们在案例中注入的templateRef

this.vcr.createEmbeddedView(this.templateRef)

创建组件

动态创建组件需要更多的仪式,因为您首先需要解析一个知道如何跨越组件实例的工厂。

为此,我们在注入的ComponentFactoryResolver 的实例上使用resolveComponentFactory 方法。

const cmpFactory = this.cfr.resolveComponentFactory(SpinnerComponent)

现在我们可以使用生成的工厂来createComponent,就像我们创建嵌入式视图一样。

this.vcr.createComponent(cmpFactory)

当然,只有在 isBusy 标志设置为 true 时才会发生这种情况,因此我们将其包装在一个分支中。

if (this.isBusy) {
  const cmpFactory = this.cfr.resolveComponentFactory(SpinnerComponent)
  this.vcr.createComponent(cmpFactory)
}

入口组件

Angular 需要先编译我们的组件,然后才能在应用程序中使用它们。如果组件从未在模板中引用,Angular 将不知道它需要编译它。我们的 Spinner 组件就是这种情况,因为我们只是从代码中动态添加它。

要明确告诉 Angular 编译组件,请将其添加到 NgModule.entryComponents

@NgModule({
  ...
  entryComponents: [SpinnerComponent],
  ...
})

完整代码

@Directive({selector: '[xuiBusy]'})
export class BusyDirective implements OnInit {

  @Input('xuiBusy') isBusy: boolean;

  constructor(private templateRef: TemplateRef<void>,
              private vcr: ViewContainerRef,
              private cfr: ComponentFactoryResolver) { }

  ngOnInit() {
    this.vcr.createEmbeddedView(this.templateRef)
    if (this.isBusy) {
      const cmpFactory = this.cfr.resolveComponentFactory(SpinnerComponent)
      this.vcr.createComponent(cmpFactory)
    }
  }

}

使用示例(也可以查看the demo

<div *xuiBusy="true">
  <span>This is some content</span>
</div>

<div *xuiBusy="false">
  <span>This is some content</span>
</div>

【讨论】:

  • 没有 TemplateRef 的提供者!如果您的 div 中没有任何内容,而该组件正是您要添加的内容,该怎么办
  • 感谢您提供真正有用的答案,您知道如何将组件粘贴到子
    中吗?
  • 非常有趣。如果创建的组件(例如微调器)需要来自外部的数据怎么办?我可以使用@Input,我在哪里设置以及如何传递这些数据?
  • @westor 放在选择器上就行了,它就明白了。我建议在不使用* 的情况下查看*ngFor 的工作方式的源代码。它基本上只是一堆输入。
  • 演示没有回答原始问题@LazarLjubenović...似乎 SpinnerComponent 跨度添加在包含指令属性的 div 下方,而不是在其中。 ?
猜你喜欢
相关资源
最近更新 更多
热门标签