使用结构指令将组件添加到TemplateRef

时间:2017-05-12 18:36:58

标签: angular angular2-directives

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

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

isBusy的值为true时,我们希望附加到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时,我们希望改变市场看起来像这样。请注意,<spinner>已添加到div元素。

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

感谢任何建议!

1 个答案:

答案 0 :(得分:7)

演示

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

设置

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

  • TemplateRef,对结构指令所在模板的引用(在desugared语法中);
  • 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>