使用结构指令将组件添加到 TemplateRef [英] Adding a Component to a TemplateRef using a structural Directive

查看:73
本文介绍了使用结构指令将组件添加到 TemplateRef的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

...

isBusy 的值为 true 时,我们希望附加到 div 的内部内容,以便我们可以在内容的顶部呈现叠加元素.

我们已经能够将组件附加到 ViewContainerRef 但是这会将繁忙元素作为 div 的兄弟元素插入,而不是在 div 中代码> 根据需要.

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

消费者做什么:

<span>这是内容</span>

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

<span>这是内容</span><spinner>请稍等...</spinner><-- 由指令插入

感谢任何建议!

解决方案

Demo

我已经在 StackBlitz 上设置了一个演示.为简洁起见,Spinner 组件、Busy 指令和使用者都在 app.component.ts 中.

设置

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

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

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

constructor(private templateRef: TemplateRef,私人录像机:ViewContainerRef,私人 cfr: ComponentFactoryResolver) { }

传递布尔值

我们需要一个输入来从外部传递数据.为了使语法更加简洁明了(尤其是当指令需要单个输入时,例如在这种情况下),我们可以将输入命名为与指令的选择器完全相同.

要重命名输入,我们可以传递一个 bindingPropertyNameInput 装饰器.

@Input('xuiBusy') isBusy: boolean;

创造消费者的内容

使用createEmbeddedView<在 ViewContainerRef 类上定义的/a> 方法.第一个参数是唯一的强制参数,它接受插入视图将基于的模板引用:这是我们在我们的案例中注入的 templateRef.

this.vcr.createEmbeddedView(this.templateRef)

创建组件

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

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

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: [Spin​​nerComponent],...})

完整代码

@Directive({selector: '[xuiBusy]'})导出类 BusyDirective 实现 OnInit {@Input('xuiBusy') isBusy: boolean;构造函数(私有模板引用:模板引用,私人录像机:ViewContainerRef,私人 cfr: ComponentFactoryResolver) { }ngOnInit() {this.vcr.createEmbeddedView(this.templateRef)如果(this.isBusy){const cmpFactory = this.cfr.resolveComponentFactory(SpinnerComponent)this.vcr.createComponent(cmpFactory)}}}

使用示例(查看演示作为好吧)

<span>这是一些内容</span>

<div *xuiBusy="false"><span>这是一些内容</span>

We are building an angular 4 component library and one of the components is a Busy component. The purpose of the component is to allow a developer to create an overlay on any given HTML element which contains a spinner graphic.

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

When the value of isBusy is true we want to append to the inner content of the div so that we can present the overlay elements on top of the content.

We have been able to append the component to the ViewContainerRef however this inserts the busy element as a sibling to the div rather than within the div as desired.

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

What the consumer does:

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

When isBusy is set to true we want to alter the market to look something like this. Notice that <spinner> has been added to the div element.

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

Any advice is appreciated!

解决方案

Demo

I've set up a demo on StackBlitz. The Spinner component, Busy directive and the consumer are all in app.component.ts for brevity.

Setup

The structural directive needs to inject the following:

  • TemplateRef, a reference to the template that the structural directive lies on (in desugared syntax);
  • ViewContainerRef, a reference to the container of views which can be rendered inside the view that the structural directive encapsulates;
  • ComponentFactoryResolver, a class which knows how to dynamically create instances of component from code.

Injecting in Angular is done via a constructor.

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

Passing the boolean

We need an input in order to pass data from the outside. In order to make the syntax pretty and obvious (especially when a directive expects a single input such as in this case), we can name the input exactly the same as our selector for the directive.

To rename the input, we can pass a bindingPropertyName to the Input decorator.

@Input('xuiBusy') isBusy: boolean;

Creating consumer's content

The content which consumer can be dynamically created by using the createEmbeddedView method defined on the ViewContainerRef class. The first parameter is the only mandatory one, and it accepts a template reference that the inserted view will be based on: this is the templateRef which we injected in our case.

this.vcr.createEmbeddedView(this.templateRef)

Creating the component

Creating a component dynamically requires a bit more ceremony, because you first need to resolve a factory which knows how to span a component instance.

For this, we use the resolveComponentFactory method on the instance of the injected ComponentFactoryResolver.

const cmpFactory = this.cfr.resolveComponentFactory(SpinnerComponent)

Now we can use the resulting factory in order to createComponent in a similar fashion we created the embedded view.

this.vcr.createComponent(cmpFactory)

Of course, this should happen only if the isBusy flag is set to true, so we wrap this in a branch.

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

Entry components

Angular needs to compile our component before they can be used in the application. If the component is never referenced in the template, Angular won't know it needs to compile it. This is the case with our Spinner component, as we're only adding it dynamically from code.

To tell explicitly Angular to compile a component, add it to NgModule.entryComponents.

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

Full code

@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)
    }
  }

}

Usage examples (check out the demo as well)

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

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

这篇关于使用结构指令将组件添加到 TemplateRef的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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