Angular2:将一个动态组件插入到DOM容器的子级
Angular2:将一个动态组件插入到DOM容器的子级
在Angular 2中,有没有一种方法可以动态地将组件作为DOM标签的子级(而不是同级)插入?有很多示例可以在给定的ViewContainerRef的标签下将动态组件插入为同级,例如(截至RC3):
@Component({
selector: '...',
template: ''
})
export class SomeComponent {
@ViewChild('placeholder', {read: ViewContainerRef}) placeholder;
constructor(private componentResolver: ComponentResolver) {}
ngAfterViewInit() {
this.componentResolver.resolveComponent(MyDynamicComponent).then((factory) => {
this.componentRef = this.placeholder.createComponent(factory);
});
}
}
但是这会生成类似于以下的DOM:
期望的结果是:
使用SomeComponent的ViewContainerRef会得到相同的结果,仍然会将生成的组件插入为同级,而不是子级。我可以接受一个模板为空,动态组件在模板中插入的解决方案(在组件选择器标记内部)。
在使用像ng2-dragula这样的库进行从动态组件列表拖拽并从模型更新的情况下,DOM结构非常重要。额外的div在可拖动元素列表中,但在模型之外,破坏了拖放逻辑。
有人说这是不可能的(参见此评论),但这听起来像是一个非常令人惊讶的限制。
Angular2: 在DOM的容器中插入一个动态组件的问题的出现原因是作者在寻找解决方案时,发现了一个被认可的答案,但对他不起作用。解决方法是使用新创建组件的元素引用,并通过渲染服务将其附加到当前元素引用。我们可以通过组件的注入器属性获取组件对象。代码如下:
import { ComponentFactoryResolver, Directive, Renderer2, ViewContainerRef } from '@angular/core'; @Directive({ selector: '[mydirective]' }) export class MyDirectiveDirective { constructor( private cfResolver: ComponentFactoryResolver, public vcRef: ViewContainerRef, private renderer: Renderer2 ) {} public appendComponent() { const factory = this.cfResolver.resolveComponentFactory(MyDynamicComponent); const componentRef = this.vcRef.createComponent(factory); this.renderer.appendChild( this.vcRef.element.nativeElement, componentRef.injector.get(MyDynamicComponent).elRef.nativeElement ); } }
请问能添加一个stackblitz吗?我无法让它工作起来。
为了使这个工作起来,MyDynamicComponent需要通过elRef公开访问其ElementRef。
在Angular2中,要在DOM的容器中插入一个动态组件,可以使用Renderer2来调整DOM结构。以上代码中的构造函数使用了四个参数:el(元素引用)、viewContainerRef(视图容器引用)、componentFactoryResolver(组件工厂解析器)、render(渲染器)。在ngOnInit生命周期钩子中,首先通过组件工厂解析器获取到MyDynamicComponent的组件工厂,然后使用视图容器引用的createComponent方法创建一个组件实例,最后使用渲染器的appendChild方法将组件实例的根节点添加到元素引用的nativeElement中。
然而,使用渲染器来调整DOM结构可能会导致内部变化检测出现问题。具体原因和优化技巧可以参考这篇博文。不过,我不确定在这种特定情况下是否会引发问题。
需要注意的是,如果宿主元素有*ngIf或其他指令将其转换为模板,则以上方法会失效。
Angular2: 在DOM的容器中插入一个动态组件的问题出现的原因是占位符使用的是<div #placeholder></div>
,而不是正确的<div><ng-template #placeholder></ng-template></div>
。解决方法是将占位符替换为正确的代码。另外,<ng-container #placeholder></ng-container>
也可以使用,这种方法更好因为<ng-container>
专门用于这种情况(不添加标签的容器),并且可以在其他情况中使用,比如NgIf
,而不需要添加真实的标签。
占位符是动态的,不是固定的。可能可以动态地创建ViewChild。不过我不知道如何做到。ViewChild有不同的方式来指定目标组件。这可能是另一个问题。
动态组件的位置应该是<my-app><div><ng-template #container></ng-template></div><!-- The Dynamic Component Position--></my-app>。ng-template会消失吗?实际上,ng-template/ng-component在渲染的HTML中会消失。它只是一种指示你想要插入组件的位置的标签。你可以检查更新后的示例,并使用浏览器开发工具进行检查以确认。
实际上,它占据了一个位置,但没有渲染。
在我提供的链接中(stackblitz),我得到的输出和你描述的不一样。组件模板是<h1><ng-template #container></ng-template></h1>
,它渲染为<h1><!----><my-dynamic>I am inserted dynamically!</my-dynamic></h1>
。动态组件处于我期望的位置,不在<h1>
的外面,而<ng-template>
消失了(被一个空注释替代)。有什么问题吗?
是的。stackblitz中的示例有效。我发现我使用的是Angular 5。我猜这可能是根本原因。
这种方法从Angular 2(或至少4,具有相同的语法)开始就有效,所以可能还有其他原因导致你得到不同的结果。也许这可以作为一个独立的问题提出。
好问题和优秀的答案。不过,有没有办法向动态组件传递参数?只需要初始化和标识组件,其余的可以通过一个公共服务来完成...
我所知道的唯一方法是在最后获取组件实例,然后手动设置值,比如myCompInstance.foo = 'Hello world!'
。顺便说一句,我猜Angular也是这样做的(因为构造函数中无法访问这些属性,只能在NgOnInit钩子中访问)。我更新了stackblitz示例来设置组件属性。
太棒了。我喜欢它的简洁性。非常感谢!