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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 01:45:02