如何用纯Angular指令将组件视图插入宿主元素内容中?
如何用纯Angular方案实现指令将指定组件插入宿主元素内容中?
我需要创建一个Angular指令,将指定组件的内容插入到其宿主元素的内部内容中。当前尝试的代码会把组件渲染在宿主元素之后,而非内部。我希望避免直接操作DOM的反模式,寻求纯Angular的解决方法。
现有实现代码
宿主组件模板使用方式
<h3 *myDirective> Content of host </h3>
指令代码(myDirective.ts)
import {Directive, ViewContainerRef, TemplateRef, ComponentFactoryResolver} from '@angular/core'; import {HoverComponent} from './hover.component'; @Directive({ selector: '[myDirective]', }) export class MyDirective { constructor( public viewContainerRef: ViewContainerRef, public templateRef: TemplateRef<any>, public componentFactoryResolver: ComponentFactoryResolver, ) { } ngOnInit() { const componentFactory = this.componentFactoryResolver.resolveComponentFactory(HoverComponent); const componentRef = this.viewContainerRef.createComponent(componentFactory); const embeddedViewRef = this.viewContainerRef.createEmbeddedView(this.templateRef); this.viewContainerRef.insert(embeddedViewRef); this.viewContainerRef.insert(componentRef.hostView); } }
HoverComponent模板
<h1>hover</h1>
预期与实际结果
预期渲染结果
<h3> Content of host <h1>hover</h1> </h3>
实际渲染结果
<h3> Content of host </h3> <h1>hover</h1>
纯Angular解决方案
问题根源在于结构型指令(带*语法)的ViewContainerRef指向的是宿主元素的父容器,而非宿主元素本身,所以插入的内容会成为宿主的兄弟节点。要实现组件插入宿主内部,我们可以:
- 先渲染宿主的模板内容,拿到宿主DOM元素;
- 创建组件时指定该宿主元素作为组件的挂载目标;
- 将组件纳入Angular的变化检测体系中。
修改后的指令代码(Angular 9+)
import {Directive, ViewContainerRef, TemplateRef, Injector, ApplicationRef} from '@angular/core'; import {HoverComponent} from './hover.component'; @Directive({ selector: '[myDirective]', }) export class MyDirective { constructor( private viewContainerRef: ViewContainerRef, private templateRef: TemplateRef<any>, private injector: Injector, private appRef: ApplicationRef ) { } ngOnInit() { // 1. 渲染宿主模板,获取宿主元素(这里是<h3>) const embeddedViewRef = this.viewContainerRef.createEmbeddedView(this.templateRef); const hostElement = embeddedViewRef.rootNodes[0] as HTMLElement; // 2. 创建HoverComponent并挂载到宿主元素内部 const componentRef = this.viewContainerRef.createComponent(HoverComponent, { injector: this.injector, hostElement: hostElement // 指定组件的宿主为当前<h3>元素 }); // 3. 将组件加入应用的变化检测树,确保视图更新 this.appRef.attachView(componentRef.hostView); // 4. 组件销毁时清理资源 componentRef.onDestroy(() => { this.appRef.detachView(componentRef.hostView); }); } }
兼容Angular 8及以下版本的代码
import {Directive, ViewContainerRef, TemplateRef, ComponentFactoryResolver, Injector, ApplicationRef} from '@angular/core'; import {HoverComponent} from './hover.component'; @Directive({ selector: '[myDirective]', }) export class MyDirective { constructor( private viewContainerRef: ViewContainerRef, private templateRef: TemplateRef<any>, private componentFactoryResolver: ComponentFactoryResolver, private injector: Injector, private appRef: ApplicationRef ) { } ngOnInit() { const embeddedViewRef = this.viewContainerRef.createEmbeddedView(this.templateRef); const hostElement = embeddedViewRef.rootNodes[0] as HTMLElement; const componentFactory = this.componentFactoryResolver.resolveComponentFactory(HoverComponent); const componentRef = componentFactory.create(this.injector, [], hostElement); this.appRef.attachView(componentRef.hostView); componentRef.onDestroy(() => { this.appRef.detachView(componentRef.hostView); }); } }
注意:Angular 8及以下版本需要将
HoverComponent添加到模块的entryComponents数组中。
内容的提问来源于stack exchange,提问作者Arto Avag
相关产品推荐
相关产品推荐

