我们正在构建一个角度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>
感谢任何建议!
答案 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)
}
}
}
<div *xuiBusy="true">
<span>This is some content</span>
</div>
<div *xuiBusy="false">
<span>This is some content</span>
</div>