使用结构指令将组件添加到 TemplateRef [英] Adding a Component to a TemplateRef using a structural Directive
问题描述
我们正在构建一个 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) { }
传递布尔值
我们需要一个输入来从外部传递数据.为了使语法更加简洁明了(尤其是当指令需要单个输入时,例如在这种情况下),我们可以将输入命名为与指令的选择器完全相同.
要重命名输入,我们可以传递一个 bindingPropertyName
到 Input
装饰器.
@Input('xuiBusy') isBusy: boolean;
创造消费者的内容
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: [SpinnerComponent],...})
完整代码
@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屋!