如何用单个<ng-content>捕获所有嵌套及根级的<pane>元素?
<ng-content>捕获所有嵌套的<pane>元素 我希望通过单个<ng-content>捕获所有<pane>元素,其中一个<pane>位于父组件DOM根节点,另一个嵌套在<div>中。
父组件HTML
<tab style="display: flex; flex-direction: column"> <pane id="1">Pane 1</pane> <div> <pane id="2">Pane 2</pane> </div> </tab>
Tab组件模板
Pane in ng-content select="pane" <ng-content select="pane" /> <br/> Others panes: <ng-content />
当前结果
Pane in ng-content select="pane"
Pane 1Others panes:
Pane 2
目前第一个带select="pane"的<ng-content>无法捕获嵌套的<pane>,后者进入了第二个<ng-content>。请问是否有办法让第一个<ng-content>捕获所有<pane>元素?
解决方案
<ng-content>的选择器仅能匹配直接子节点,无法自动捕获嵌套在其他元素内的内容。要实现捕获所有层级的<pane>,需要结合@ContentChildren和动态视图渲染:
1. 在Tab组件中查询所有<pane>后代
在Tab组件类中,使用@ContentChildren并设置descendants: true,这样可以获取所有嵌套层级的<pane>组件实例:
import { Component, ContentChildren, QueryList, ViewChild, ViewContainerRef, AfterContentInit } from '@angular/core'; import { PaneComponent } from './pane.component'; @Component({ selector: 'tab', templateUrl: './tab.component.html' }) export class TabComponent implements AfterContentInit { @ContentChildren(PaneComponent, { descendants: true }) panes!: QueryList<PaneComponent>; @ViewChild('paneContainer', { read: ViewContainerRef }) paneContainer!: ViewContainerRef; ngAfterContentInit() { // 将所有pane渲染到指定容器 this.panes.forEach(pane => { this.paneContainer.createEmbeddedView(pane.templateRef); }); } }
2. 修改Pane组件暴露模板引用
让Pane组件内部用<ng-template>包裹内容,并暴露模板引用供父组件使用:
import { Component, TemplateRef, ViewChild } from '@angular/core'; @Component({ selector: 'pane', template: `<ng-template #paneTemplate><ng-content></ng-content></ng-template>` }) export class PaneComponent { @ViewChild('paneTemplate') templateRef!: TemplateRef<any>; }
3. 更新Tab组件模板
替换原有的<ng-content>,改用容器来动态渲染所有<pane>:
Pane in dynamic container: <div #paneContainer></div> <br/> Others content: <ng-content></ng-content>
原理说明
@ContentChildren的descendants: true配置会让Angular查询组件内容中的所有后代<pane>实例,而不仅仅是直接子节点。之后通过ViewContainerRef动态创建嵌入式视图,就能把所有<pane>的内容集中渲染到同一个区域,实现预期效果。
参考Angular官方文档:@ContentChildren用于查询组件内容中的子元素,当设置descendants: true时,会遍历所有后代节点进行匹配。
内容的提问来源于stack exchange,提问作者Normand Julian

