You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular多插槽投影与指令问题:标签菜单模板渲染异常求助

问题描述

我要实现一个点击标签展开对应内容的菜单,使用方式如下:

<dialog-menu>
  <dialog-menu-item>
    <div *appHeader>Test 1</div>
    <div *appContent>Content 1</div>
  </dialog-menu-item>
  <dialog-menu-item>
    <div *appHeader>Test 2</div>
    <div *appContent>Content 2</div>
  </dialog-menu-item>
</dialog-menu>

希望将所有头部和内容分别放在不同区块展示,当前dialog-menu.component.html模板如下:

<div>
  <ng-container *ngFor="let item of menuItems">
    <div>Header:</div>
    <ng-container [ngTemplateOutlet]="item.header"></ng-container>
  </ng-container>
</div>
<div>
  <ng-container *ngFor="let item of menuItems">
    <div>Content:</div>
    <ng-container [ngTemplateOutlet]="item.content"></ng-container>
  </ng-container>
</div>

指令及组件代码如下:

@Directive({
  selector: '[appHeader]'
})
export class DialogMenuHeaderDirective {
  constructor(public templateRef: TemplateRef<any>){}
}

@Directive({
  selector: '[appContent]'
})
export class DialogMenuContentDirective {
  constructor(public templateRef: TemplateRef<any>){}
}

@Component({
  selector: 'dialog-menu-item',
  template: "<ng-template><ng-content></ng-content></ng-template>"
})
export class DialogMenuItemComponent {
  @ContentChild(DialogMenuHeaderDirective) header: TemplateRef<any>;
  @ContentChild(DialogMenuContentDirective) content: TemplateRef<any>;
}

@Component({
  selector: 'dialog-menu',
  templateUrl: './dialog-menu.component.html'
})
export class DialogMenuComponent {
  @ContentChildren(DialogMenuItemComponent) menuItems: QueryList<DialogMenuItemComponent>;
}

目前仅能渲染出"Header:"和"Content:"的文本,ngTemplateOutlet无法加载对应内容。

问题原因
  1. DialogMenuItemComponent中@ContentChild的类型错误:声明的是TemplateRef<any>,但实际获取的是DialogMenuHeaderDirective/DialogMenuContentDirective实例,导致后续无法正确拿到模板引用。
  2. dialog-menu-item的模板用<ng-template>包裹了<ng-content>,这会把插槽内容封装在内部模板中,导致@ContentChild无法穿透找到里面的指令。
修复方案

1. 修正DialogMenuItemComponent的类型及模板

修改DialogMenuItemComponent的@ContentChild类型,并移除多余的<ng-template>包裹:

@Component({
  selector: 'dialog-menu-item',
  template: "<ng-content></ng-content>" // 去掉外层ng-template
})
export class DialogMenuItemComponent {
  // 类型改为对应的指令类型
  @ContentChild(DialogMenuHeaderDirective) header!: DialogMenuHeaderDirective;
  @ContentChild(DialogMenuContentDirective) content!: DialogMenuContentDirective;
}

2. 调整dialog-menu模板中的模板引用

因为现在item.header是指令实例,需要取它的templateRef属性:

<div>
  <ng-container *ngFor="let item of menuItems">
    <div>Header:</div>
    <ng-container [ngTemplateOutlet]="item.header.templateRef"></ng-container>
  </ng-container>
</div>
<div>
  <ng-container *ngFor="let item of menuItems">
    <div>Content:</div>
    <ng-container [ngTemplateOutlet]="item.content.templateRef"></ng-container>
  </ng-container>
</div>

3. 可选:添加点击展开逻辑

如果需要实现点击头部切换内容显示,可在DialogMenuComponent中添加状态管理:

export class DialogMenuComponent {
  @ContentChildren(DialogMenuItemComponent) menuItems!: QueryList<DialogMenuItemComponent>;
  // 记录每个item的展开状态,默认全部收起
  expandedStates = new Map<DialogMenuItemComponent, boolean>();

  ngAfterContentInit() {
    this.menuItems.forEach(item => this.expandedStates.set(item, false));
  }

  toggleItem(item: DialogMenuItemComponent) {
    this.expandedStates.set(item, !this.expandedStates.get(item));
  }
}

同时修改模板,绑定点击事件和显示状态:

<div>
  <ng-container *ngFor="let item of menuItems">
    <div (click)="toggleItem(item)" style="cursor: pointer;">Header:</div>
    <ng-container [ngTemplateOutlet]="item.header.templateRef"></ng-container>
  </ng-container>
</div>
<div>
  <ng-container *ngFor="let item of menuItems">
    <div *ngIf="expandedStates.get(item)">Content:</div>
    <ng-container *ngIf="expandedStates.get(item)" [ngTemplateOutlet]="item.content.templateRef"></ng-container>
  </ng-container>
</div>

这样就能正常渲染头部和内容,并实现点击展开的功能了。

内容的提问来源于stack exchange,提问作者graem

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 14:02:53