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无法加载对应内容。
问题原因
DialogMenuItemComponent中@ContentChild的类型错误:声明的是TemplateRef<any>,但实际获取的是DialogMenuHeaderDirective/DialogMenuContentDirective实例,导致后续无法正确拿到模板引用。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
相关产品推荐
相关产品推荐

