Angular 17中`defer`与`ng-content`的条件渲染封装问题
Angular 17 条件式Defer封装方案(支持SSR)
针对你需要封装条件化defer、避免重复模板逻辑的需求,推荐两种符合Angular惯用方式的高效方案:
方案一:通用组件(带模板投影)
这种方案通过组件封装@if/@else/@defer逻辑,利用模板投影传递内容,解决原生ng-content无法重复渲染的问题,同时兼容SSR。
组件实现
import { Component, Input, ContentChild, TemplateRef } from '@angular/core'; @Component({ selector: 'conditionally-defer', standalone: true, template: ` @if (!shouldDefer) { <ng-container *ngTemplateOutlet="contentTemplate"></ng-container> } @else { @defer { <ng-container *ngTemplateOutlet="contentTemplate"></ng-container> } <!-- 可选:添加loading状态 --> @loading { <div>加载中...</div> } } `, }) export class ConditionallyDeferComponent { @Input() shouldDefer = false; @ContentChild(TemplateRef) contentTemplate!: TemplateRef<any>; }
使用方式
<conditionally-defer [shouldDefer]="isDeferNeeded"> <ng-template> <!-- 这里放置需要条件延迟渲染的内容 --> <div class="heavy-content">我的延迟加载内容</div> </ng-template> </conditionally-defer>
方案二:结构型指令
这种方案更贴近Angular内置指令(如*ngIf)的使用习惯,语法更简洁,侵入性更低,同样支持SSR和条件延迟渲染。
指令实现
import { Directive, Input, TemplateRef, ViewContainerRef, NgZone } from '@angular/core'; @Directive({ selector: '[conditionallyDefer]', standalone: true, }) export class ConditionallyDeferDirective { private currentView: EmbeddedViewRef<any> | null = null; constructor( private templateRef: TemplateRef<any>, private viewContainerRef: ViewContainerRef, private ngZone: NgZone ) {} @Input() set conditionallyDefer(shouldDefer: boolean) { // 清理旧视图,避免内存泄漏 this.viewContainerRef.clear(); this.currentView = null; if (!shouldDefer) { // 立即渲染,SSR可捕获内容 this.currentView = this.viewContainerRef.createEmbeddedView(this.templateRef); } else { // 模拟原生defer的idle触发逻辑,客户端延迟渲染 this.ngZone.runOutsideAngular(() => { if ('requestIdleCallback' in window) { requestIdleCallback(() => { this.ngZone.run(() => { this.currentView = this.viewContainerRef.createEmbeddedView(this.templateRef); }); }); } else { // 降级处理:兼容不支持requestIdleCallback的环境 setTimeout(() => { this.ngZone.run(() => { this.currentView = this.viewContainerRef.createEmbeddedView(this.templateRef); }); }, 200); } }); } } }
使用方式
<!-- 直接在目标元素上使用指令,语法和*ngIf一致 --> <div *conditionallyDefer="isDeferNeeded"> <div class="heavy-content">我的延迟加载内容</div> </div>
方案选择建议
- 若需要额外的模板逻辑(如加载状态、错误状态),优先选择通用组件,扩展性更强;
- 若追求简洁的语法和低侵入性,优先选择结构型指令,符合Angular惯用写法。
注意事项
- 两种方案均支持SSR:当
shouldDefer为false时,内容会被正常渲染到SSR输出中;为true时,仅在客户端延迟渲染,和原生defer行为一致; - 若需要支持原生
defer的其他触发条件(如on viewport、on hover),可通过扩展组件/指令的输入参数实现对应逻辑。
内容的提问来源于stack exchange,提问作者Philipp
相关产品推荐
相关产品推荐

