Angular中ng-content结合*ngIf时部分场景不渲染问题排查
Angular中ng-content与*ngIf结合渲染异常问题
需求说明
实现Angular组件的条件渲染逻辑:
- 当输入属性
conf存在时,用指定的包装容器渲染投影内容 - 当
conf不存在时,直接渲染投影内容
但当前实现中,其中一种条件下的投影内容始终无法显示,尝试用ng-template改写后问题仍未解决。
组件代码
@Component({ selector: 'aurora-page-sub-header', templateUrl: './page-sub-header.component.html', styleUrls: ['./page-sub-header.component.scss'], encapsulation: ViewEncapsulation.None }) export class PageSubHeaderComponent implements OnInit { collapse: boolean = false; @Input() conf?: any; constructor() {} ngOnInit() {} }
初始HTML模板
<div *ngIf="conf"> <div class="aurora-c-page-sub-header-container"> <h3 class="aurora-c-page-sub-header-search">{{ 'ADVANCED_PAYMENTS_CONTROL.INQUIRIES_SEARCH.SEARCH' | translate }}</h3> <h3 class="aurora-c-page-sub-header-notes">{{ 'ADVANCED_PAYMENTS_CONTROL.INQUIRIES_SEARCH.ACTION_NOTES' | translate }}</h3> <a (click)="collapse=!collapse">Collapse</a> </div> <div class="aurora-c-page-sub-header" [hidden]="collapse"> <ng-content></ng-content> </div> </div> <div *ngIf="!conf"> <ng-content></ng-content> </div>
更新后的HTML模板(问题未解决)
<ng-template #projected> <ng-content></ng-content> </ng-template> <div *ngIf="conf"> <div class="aurora-c-page-sub-header-container"> <h3 class="aurora-c-page-sub-header-search">{{ 'AURORA.ADVANCED_PAYMENTS_CONTROL.INQUIRIES_SEARCH.SEARCH' | translate }}</h3> <h3 class="aurora-c-page-sub-header-notes">{{ 'AURORA.ADVANCED_PAYMENTS_CONTROL.INQUIRIES_SEARCH.ACTION_NOTES' | translate }}</h3> <a (click)="collapse=!collapse">Collapse</a> </div> <div class="aurora-c-page-sub-header" [hidden]="collapse"> <ng-container *ngTemplateOutlet="projected"></ng-container> </div> </div> <div *ngIf="!!conf"> <ng-container *ngTemplateOutlet="projected"> </ng-container> </div>
问题根源
初始模板核心问题:Angular的
ng-content是一次性静态投影——组件初始化时,外部传入的投影内容只会被第一个ng-content捕获,模板中后续的ng-content无法获取到任何内容。因此当conf不存在时,第二个<div *ngIf="!conf">里的ng-content始终为空。更新后模板问题:将
ng-content放入ng-template并不能解决投影次数限制的问题,ng-content依然只会在组件初始化时捕获一次内容。同时更新后的模板存在笔误:最后一个*ngIf="!!conf"应该是*ngIf="!conf",逻辑完全写反了。
正确解决方案
方案1:通过模板引用捕获投影内容(推荐)
利用ContentChild捕获外部传入的模板内容,再通过*ngTemplateOutlet复用渲染,彻底解决投影内容复用问题。
修改组件代码
import { Component, OnInit, Input, ContentChild, TemplateRef } from '@angular/core'; @Component({ selector: 'aurora-page-sub-header', templateUrl: './page-sub-header.component.html', styleUrls: ['./page-sub-header.component.scss'], encapsulation: ViewEncapsulation.None }) export class PageSubHeaderComponent implements OnInit { collapse: boolean = false; @Input() conf?: any; // 捕获外部传入的ng-template内容 @ContentChild(TemplateRef) projectedContent!: TemplateRef<any>; constructor() {} ngOnInit() {} }
修改组件模板
<!-- conf存在时的渲染逻辑 --> <div *ngIf="conf"> <div class="aurora-c-page-sub-header-container"> <h3 class="aurora-c-page-sub-header-search">{{ 'ADVANCED_PAYMENTS_CONTROL.INQUIRIES_SEARCH.SEARCH' | translate }}</h3> <h3 class="aurora-c-page-sub-header-notes">{{ 'ADVANCED_PAYMENTS_CONTROL.INQUIRIES_SEARCH.ACTION_NOTES' | translate }}</h3> <a (click)="collapse=!collapse">Collapse</a> </div> <div class="aurora-c-page-sub-header" [hidden]="collapse"> <ng-container *ngTemplateOutlet="projectedContent"></ng-container> </div> </div> <!-- conf不存在时的渲染逻辑 --> <ng-container *ngIf="!conf"> <ng-container *ngTemplateOutlet="projectedContent"></ng-container> </ng-container>
使用组件时的写法
需要将投影内容放入<ng-template>中:
<aurora-page-sub-header [conf]="yourConf"> <ng-template> <!-- 这里是你的投影内容 --> <p>示例投影内容</p> <button>操作按钮</button> </ng-template> </aurora-page-sub-header>
方案2:简化条件渲染(无需修改组件类)
如果不想修改组件类代码,可以通过单个ng-content配合*ngIf的else语法,确保投影内容只被捕获一次:
<!-- conf存在时的容器 --> <div *ngIf="conf; else directRender"> <div class="aurora-c-page-sub-header-container"> <h3 class="aurora-c-page-sub-header-search">{{ 'ADVANCED_PAYMENTS_CONTROL.INQUIRIES_SEARCH.SEARCH' | translate }}</h3> <h3 class="aurora-c-page-sub-header-notes">{{ 'ADVANCED_PAYMENTS_CONTROL.INQUIRIES_SEARCH.ACTION_NOTES' | translate }}</h3> <a (click)="collapse=!collapse">Collapse</a> </div> <div class="aurora-c-page-sub-header" [hidden]="collapse"> <ng-content></ng-content> </div> </div> <!-- conf不存在时的直接渲染 --> <ng-template #directRender> <ng-content></ng-content> </ng-template>
注意:此方案中
ng-content仍然只被渲染一次,Angular会将投影内容分配到第一个匹配的位置,但通过else模板的方式确保无论条件如何,内容都能正确显示。
内容的提问来源于stack exchange,提问作者tlq
相关产品推荐
相关产品推荐

