如何判断Angular中ng-content投影插槽是否被使用?
问题描述
我有一段不能修改的Angular模板结构:
<ul> <li><ng-content select="[prefix]"></ng-content></li> @for (item of items) { <li>{{item}}</li> } <li><ng-content select="[suffix]"></ng-content></li> </ul>
现在要实现:只有当prefix和suffix插槽真的有内容被投影进来时,才渲染包裹它们的<li>标签,不能让空的<li>一直显示。想要的效果大概是这样:
<ul> @if (hasPrefixContent) { <li><ng-content select="[prefix]"></ng-content></li> } @for (item of items) { <li>{{item}}</li> } @if (hasSuffixContent) { <li><ng-content select="[suffix]"></ng-content></li> } </ul>
补充背景:拿自定义标签页组件MyTabs举例,组件代码是这样的:
class MyTabs { @ContentChildren(MyTabComponent) tabs!: QueryList<MyTabComponent>; // ...其他逻辑 }
模板结构:
<div class="my-tabs"> <!-- ...其他内容 --> <ul class="flex gap-2"> <li><ng-content select="[prefix]"></ng-content></li> @for (tab of tabs; track $index) { <li>...</li> } <li><ng-content select="[suffix]"></ng-content></li> </ul> <!-- ...其他内容 --> </div>
使用示例:
<my-tabs> <span prefix>Loading...</span> <my-tab key="forms" label="Forms" route="forms"></my-tab> <my-tab key="table" label="Table" route="table"></my-tab> <my-tab key="modal" label="Modal" route="modal"></my-tab> <div suffix class="flex">My custom list of links...</div> <router-outlet></router-outlet> </my-tabs>
核心需求:针对Angular的ng-content特性,判断指定的投影插槽有没有被使用,从而避免渲染空的包裹元素。下面这些方案不算有效解决:
- 改模板结构的(没针对
ng-content解决问题) - 要求用户必须用特定元素当
prefix/suffix的(破坏组件易用性) - 只处理示例模板的(没解决核心问题)
解决方案
要判断带属性选择器的ng-content插槽有没有内容,可以用Angular的@ContentChild或者@ContentChildren装饰器去查询投影进来的内容,然后根据查询结果决定要不要渲染包裹元素。
第一步:在组件类里查询投影内容
在组件类中,用@ContentChildren(支持匹配多个元素)查询带有prefix和suffix属性的元素:
import { Component, ContentChildren, ElementRef, QueryList } from '@angular/core'; @Component({ // ...组件的元数据(选择器、模板这些) }) export class MyTabs { @ContentChildren(ElementRef, { selector: '[prefix]' }) prefixElements!: QueryList<ElementRef>; @ContentChildren(ElementRef, { selector: '[suffix]' }) suffixElements!: QueryList<ElementRef>; // 判断有没有prefix内容 get hasPrefixContent(): boolean { return this.prefixElements.length > 0; } // 判断有没有suffix内容 get hasSuffixContent(): boolean { return this.suffixElements.length > 0; } // ...你原来的其他逻辑(比如tabs的查询) }
- 用
ElementRef作为查询目标,是因为prefix/suffix可以加在任意元素上,不用限制用户用特定组件 { selector: '[prefix]' }指定查询带prefix属性的元素,和ng-content的select参数保持一致,保证查询准确
第二步:在模板里加条件判断
修改模板,用@if(Angular 17及以上版本)或者*ngIf(旧版本)判断是否渲染包裹的<li>:
<ul class="flex gap-2"> @if (hasPrefixContent) { <li><ng-content select="[prefix]"></ng-content></li> } @for (tab of tabs; track $index) { <li>...</li> } @if (hasSuffixContent) { <li><ng-content select="[suffix]"></ng-content></li> } </ul>
注意事项
- 版本兼容:如果用的是Angular 16或更早版本,把
@if换成*ngIf就行:<li *ngIf="hasPrefixContent"><ng-content select="[prefix]"></ng-content></li> - 动态内容处理:如果投影的内容是动态加载的(比如异步数据渲染),
QueryList会自动更新,hasPrefixContent和hasSuffixContent也会跟着变,不用额外写逻辑 - 不影响易用性:用户还是可以在任意元素上添加
prefix/suffix属性,完全不改变组件的使用方式
内容的提问来源于stack exchange,提问作者Alex Pappas
相关产品推荐
相关产品推荐

