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

自定义CdkStepper组件实现步骤懒加载的优化方案咨询

自定义CdkStepper的懒加载方案及实现

更优懒加载方案

不依赖Angular Material的情况下,最优雅的方式是自定义一个结构型指令,结合ng-template实现步骤内容的延迟渲染,彻底摆脱硬编码索引的ngIf写法,同时和CdkStepper的选中状态自动联动。

matTabContent/matStepContent的核心实现逻辑

这类Material指令的本质是延迟模板渲染:

  • 指令会捕获宿主ng-template的引用
  • 监听父容器(Tab组/Stepper)的选中状态变化
  • 当对应项被选中时,通过ViewContainerRef将模板内容插入DOM
  • 默认会缓存已渲染的内容,切换时不会重复销毁重建(可通过配置修改)

自定义懒加载指令的实现步骤

1. 创建LazyStepContent指令

这个指令用来标记需要懒加载的步骤内容,同时暴露模板引用供Stepper组件调用:

import { Directive, TemplateRef } from '@angular/core';

@Directive({
  selector: '[lazyStepContent]'
})
export class LazyStepContentDirective {
  constructor(public templateRef: TemplateRef<unknown>) { }
}

2. 修改自定义Stepper组件

在MyStepperComponent中,需要获取步骤的懒加载模板、监听选中状态变化,动态渲染内容:

import { Component, ContentChildren, QueryList, ViewChild, ViewContainerRef, AfterViewInit, OnChanges, SimpleChanges } from '@angular/core';
import { CdkStepper, CdkStep } from '@angular/cdk/stepper';
import { LazyStepContentDirective } from './lazy-step-content.directive';

@Component({
  selector: 'my-stepper',
  template: `
    <!-- 自定义步进器头部 -->
    <div class="stepper-header">
      <button 
        *ngFor="let step of steps; let i = index"
        (click)="selectedIndex = i"
        [class.active]="selectedIndex === i"
      >
        步骤 {{i + 1}}
      </button>
    </div>
    <!-- 步骤内容容器 -->
    <div class="stepper-content">
      <ng-container #contentContainer></ng-container>
    </div>
  `,
  styles: [`
    .stepper-header { display: flex; gap: 1rem; margin-bottom: 1rem; }
    .stepper-header button { padding: 0.5rem 1rem; border: none; background: #eee; cursor: pointer; }
    .stepper-header button.active { background: #2196F3; color: white; }
  `],
  providers: [{ provide: CdkStepper, useExisting: MyStepperComponent }]
})
export class MyStepperComponent extends CdkStepper implements AfterViewInit, OnChanges {
  @ViewChild('contentContainer', { read: ViewContainerRef }) contentContainer!: ViewContainerRef;
  @ContentChildren(CdkStep) steps!: QueryList<CdkStep>;
  @ContentChildren(LazyStepContentDirective) lazyContents!: QueryList<LazyStepContentDirective>;

  ngAfterViewInit(): void {
    this.renderActiveStep();
    this.selectedIndexChange.subscribe(() => this.renderActiveStep());
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes['selectedIndex']) {
      this.renderActiveStep();
    }
  }

  private renderActiveStep(): void {
    if (!this.contentContainer || this.selectedIndex < 0 || this.selectedIndex >= this.lazyContents.length) return;

    this.contentContainer.clear();
    const activeContent = this.lazyContents.get(this.selectedIndex);
    if (activeContent) {
      this.contentContainer.createEmbeddedView(activeContent.templateRef);
    }
  }
}

3. 在模板中使用懒加载指令

用ng-template包裹步骤内容,并添加lazyStepContent标记:

<my-stepper>
  <cdk-step>
    <ng-template lazyStepContent>
      <component-one></component-one>
    </ng-template>
  </cdk-step>
  <cdk-step>
    <ng-template lazyStepContent>
      <component-two></component-two>
    </ng-template>
  </cdk-step>
</my-stepper>

4. 可选:缓存已加载内容

如果希望切换步骤时保留已加载的组件状态,可以添加缓存逻辑:

private cachedViews = new Map<number, any>();

private renderActiveStep(): void {
  if (!this.contentContainer || this.selectedIndex < 0 || this.selectedIndex >= this.lazyContents.length) return;

  this.contentContainer.clear();
  if (this.cachedViews.has(this.selectedIndex)) {
    this.contentContainer.insert(this.cachedViews.get(this.selectedIndex));
    return;
  }

  const activeContent = this.lazyContents.get(this.selectedIndex);
  if (activeContent) {
    const viewRef = this.contentContainer.createEmbeddedView(activeContent.templateRef);
    this.cachedViews.set(this.selectedIndex, viewRef);
  }
}

内容的提问来源于stack exchange,提问作者Mark Durkot

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 17:05:31