news 2026/9/14 12:47:04

如何继承 CdkStepper 构建自定义 Stepper 组件并支持 linear 模式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何继承 CdkStepper 构建自定义 Stepper 组件并支持 linear 模式

如何继承 CdkStepper 构建自定义 Stepper 组件并支持 linear 模式

【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components

在 Angular 项目中,如果你需要一个分步向导(wizard)流程,但不希望被 Angular Material 的 Material Design 样式绑定,CDK stepper 是官方给出的基座:它只提供「哪个步骤处于激活状态」这一层行为(包括键盘交互和前进/回退 API),外观完全由你自己用 CSS 控制。本文基于仓库内文档 CDK stepper 自定义指南 和 stepper.md,给出一条从「继承CdkStepper建组件」到「开启 linear 模式并验证其行为」的完整操作路径。

完成后可得到的结果:一个自定义 Stepper 组件,步骤内容可以随意组织,普通模式下用户可自由切换步骤,linear 模式下必须完成前一步才能进入下一步。

准备:可用的 API 面

自定义 Stepper 只需要一个前置条件:项目可以引入@angular/cdk/stepper入口(仓库中的示例即从该路径导入):

import {CdkStepper, CdkStepperModule} from '@angular/cdk/stepper';

按指南说明,组件类继承CdkStepper之后,就可以直接访问基类上定义的属性,包括linearselectedIndexsteps。模板里还会用到selected(当前激活的CdkStep)以及cdkStepperPrevious/cdkStepperNext两个按钮指令——它们放在步骤内时会自动绑定点击事件,驱动工作流前进或回退。

创建继承自 CdkStepper 的组件

自定义组件的核心是两点:类上继承CdkStepper,并在providers中把自己注册为CdkStepper。仓库中的参考实现 cdk-custom-stepper-without-form-example.ts 如下:

@Component({ selector: 'example-custom-stepper', templateUrl: './example-custom-stepper.html', styleUrl: './example-custom-stepper.css', providers: [{provide: CdkStepper, useExisting: CustomStepper}], imports: [NgTemplateOutlet, CdkStepperModule], }) export class CustomStepper extends CdkStepper { selectStepByIndex(index: number): void { this.selectedIndex = index; } }

providers中的{provide: CdkStepper, useExisting: CustomStepper}是必需的一步:指南原话是「This custom stepper provides itself as CdkStepper so that it can be recognized by other components」,即靠这条 provider 让组件能被其他组件按CdkStepper识别。

selectStepByIndex用于点击步骤按钮时手动切换:直接给selectedIndex赋值即可。注意这个 setter 有边界保护,在 stepper.ts 中,传入越界值会抛出错误:

cdkStepper: Cannot assign out-of-bounds value to `selectedIndex`.

后续排查导航问题时,如果你看到这条报错,说明传入的步骤下标超出了步骤数量。

模板与样式:如何渲染当前步骤

模板的职责是把「当前步骤的内容」投射出来,并放上下一步/上一步按钮。参考实现 example-custom-stepper.html:

<section class="example-container"> <header> <h2>Step {{ selectedIndex + 1 }}/{{ steps.length }}</h2> </header> <div [ngTemplateOutlet]="selected ? selected.content : null"></div> <footer class="example-step-navigation-bar"> <button class="example-nav-button" cdkStepperPrevious>&larr;</button> @for (step of steps; track step; let i = $index) { <button class="example-step" [class.example-active]="selectedIndex === i" (click)="selectStepByIndex(i)">Step {{ i + 1 }}</button> } <button class="example-nav-button" cdkStepperNext>&rarr;</button> </footer> </section>

各部分的作用:

  • <div [ngTemplateOutlet]="selected ? selected.content : null">CdkStep的内容会投射到当前选中步骤的content上,这里负责把它渲染出来;
  • @for (step of steps; ...):遍历steps生成步骤按钮,当前步骤通过selectedIndex === i加高亮类,点击时调用selectStepByIndex(i)
  • cdkStepperPrevious/cdkStepperNext:两个按钮指令,点击后自动回退/前进,无需手写导航逻辑。

样式文件(example-custom-stepper.css)完全是你自己的实现,CDK 不强制任何样式:

.example-container { border: 1px solid; padding: 10px; margin: 10px; } .example-step-navigation-bar { display: flex; justify-content: flex-start; margin-top: 10px; } .example-step { background: transparent; border: 0; margin: 0 10px; padding: 10px; color: inherit; } .example-step.example-active { border-bottom: 1px solid; font-weight: 600; } .example-nav-button { background: transparent; border: 0; color: inherit; }

指南中还给出一个等价的简化写法:类方法叫onClick(index: number)、模板用[style.display]="selected ? 'block' : 'none'"包裹<ng-container [ngTemplateOutlet]="selected.content">。两种写法功能相同,类名/方法名按你的项目命名习惯取即可;本文后续步骤统一采用仓库参考实现的命名。

在页面模板中使用自定义 Stepper

在宿主组件模板里引入<example-custom-stepper>,每个步骤必须用<cdk-step>包裹。最小用法(参考示例模板):

<example-custom-stepper> <cdk-step> <p>This is any content of "Step 1"</p> </cdk-step> <cdk-step> <p>This is any content of "Step 2"</p> </cdk-step> </example-custom-stepper>

步骤内容可以是任意内容。可选分支:如果你的步骤来自数据集合,指南展示了用@for迭代并在每个cdk-step里放自定义步骤组件的写法:

<example-custom-stepper> @for (step of mySteps; track step) { <cdk-step> <my-step-component [step]="$index"></my-step-component> </cdk-step> } </example-custom-stepper>

此时组件的导入按仓库示例的写法:宿主组件通过forwardRef引用同一文件中定义的CustomStepper,并导入CdkStepperModule

imports: [forwardRef(() => CustomStepper), CdkStepperModule],

开启 linear 模式并定义「步骤完成」

在组件标签上加linear属性即可开启线性模式。stepper.md对这一模式的行为定义是:用户必须完成前面的步骤才能继续。那么「完成」由谁判断?文档给出两种机制,且二者可以共存,completedstepControl同时设置,stepControl优先

方式一:不依赖表单,用 completed 属性

指南中的无表单示例:每个步骤设editable="false",并把[completed]绑定到一个外部状态。在完成之前(completedfalse),用户无法进入下一步;点击「Complete step」把状态置为true后才能前进:

<example-custom-stepper linear> <cdk-step editable="false" [completed]="completed"> <input type="text" name="a" value="Cannot proceed to next step" /> <button (click)="completeStep()">Complete step</button> </cdk-step> <cdk-step editable="false"> <input type="text" name="b" value="b" /> </cdk-step> </example-custom-stepper>
export class App { completed = false; completeStep(): void { this.completed = true; } }

方式二:用表单校验,绑定 stepControl

仓库示例 cdk-linear-stepper-with-form 展示了给每个步骤绑定顶层AbstractControl的写法,步骤有效后线性模式才放行:

isLinear = true; firstFormGroup = this._formBuilder.group({ firstControl: ['', Validators.required], }); secondFormGroup = this._formBuilder.group({ secondControl: ['', Validators.required], });
<example-custom-linear-stepper [linear]="isLinear"> <cdk-step [stepControl]="firstFormGroup"> <label for="stepOneInput">Step 1 input</label> <form [formGroup]="firstFormGroup"> <input placeholder="Input" formControlName="firstControl" id="stepOneInput" required> </form> </cdk-step> <cdk-step [stepControl]="secondFormGroup"> <label for="stepTwoInput">Step 2 input</label> <form [formGroup]="secondFormGroup"> <input placeholder="Input" formControlName="secondControl" id="stepTwoInput" required> </form> </cdk-step> </example-custom-linear-stepper>

使用表单时文档明确了两条规则,配置时必须区分:

  • 单个表单覆盖整个 Stepper:步骤中间的 next/previous 按钮必须设为type="button",否则会在所有步骤完成前提前触发表单提交;
  • 每个步骤一个表单:任意一个表单提交即推动工作流前进。

与 linear 模式直接相关的三个步骤属性

stepper.md还定义了三个影响线性流程判断的属性:

  • optional:linear stepper 中如果不要求必须完成某一步,在CdkStep上设置optional
  • editable:步骤默认可编辑(用户可以回到已完成步骤修改)。设editable="false"关闭该行为,上方无表单示例即用到了它;
  • completed:默认情况下,linear stepper 中当步骤有效(isValid(this.stepControl))且用户已交互过时返回true。你也可以像方式一那样显式赋值来覆盖默认行为。

验证行为

完成组装后,按文档定义的行为核对以下现象:

  1. 普通模式:点击各步骤按钮或左右导航按钮,头部的Step {n}/{总数}随之变化,当前步骤内容通过ngTemplateOutlet切换显示;左右导航受cdkStepperNext/cdkStepperPrevious指令驱动,无需手动处理。
  2. linear 模式 + completed:在completedfalse时,点击下一步无法前进(文档原话:该属性「won't allow the user to continue until it becomes true」);点击「Complete step」后再点下一步才前进。
  3. linear 模式 + stepControl:表单必填项未填时步骤无效,线性模式不放行;填写有效后前进。
  4. 越界赋值:手动给selectedIndex赋越界值会抛出cdkStepper: Cannot assign out-of-bounds value to+ 'selectedIndex' +.,可用于确认自己的赋值逻辑没有算错下标。
  5. 重置:需要把 Stepper 恢复到初始状态时调用reset方法。注意文档提醒:重置会调用底层表单控件的reset,其值会被清空——如果你的步骤依赖表单数据,重置前需要有重建数据的逻辑。

键盘交互与可访问性

CDK stepper 自带键盘支持,stepper.md给出的快捷键为:

快捷键行为
Left Arrow聚焦上一个步骤头部
Right Arrow聚焦下一个步骤头部
Enter选中当前聚焦的步骤
Space选中当前聚焦的步骤

除键盘支持外,CDK 层不再施加其他无障碍处理。stepper.md建议在实现自己的组件时,把 Stepper 按 tabbed view 处理:整体容器用role="tablist",可点击的步骤头部用role="tab",选中后展开的内容区用role="tabpanel",步骤头部再加aria-selected反映选中状态。文档指向仓库中的 Angular Material stepper(src/material/stepper)作为可参考的可访问实现。

参考实现入口

  • 自定义 Stepper(无表单)完整示例:cdk-custom-stepper-without-form-example.ts、模板、样式
  • linear + 表单示例:cdk-linear-stepper-with-form-example.ts、模板
  • 基类行为与步骤状态定义:stepper.ts(linearselectedIndexselectednextpreviousreset

如果后续需要带 Material Design 样式的标准 Stepper 而不是完全自定样式,可以对照仓库中的MatStepper文档与示例;本文路径只覆盖 CDK 层的自定义场景。

【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 12:45:49

腾讯云AIGC全栈方案:漫剧工业化生产实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 12:41:28

电力系统经济调度的二进制遗传算法优化实践

1. 问题背景与核心挑战电力系统经济调度是能源管理领域的经典优化问题&#xff0c;其核心目标是在满足电力需求的前提下&#xff0c;合理分配各发电机组的出力&#xff0c;使得总发电成本最低。传统经济调度模型主要考虑燃料成本最小化&#xff0c;但随着环保要求提高和电网规模…

作者头像 李华
网站建设 2026/9/14 12:41:17

唐代放妻书:从法律文书看古代婚姻制度

1. 项目背景解析 《唐朝诡事录之西行》作为一部以唐代为背景的悬疑探案剧&#xff0c;其剧情中出现的"独孤羊放妻春条书"这一文书道具&#xff0c;实际上反映了唐代婚姻制度中一个鲜为人知的特殊现象——"放妻书"。这种文书在唐代属于正式的法律文书&#…

作者头像 李华
网站建设 2026/9/14 12:41:15

DFS、BFS与并查集核心模板详解:连通性问题的三把钥匙

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 12:38:55

AI Agent工程化落地实战:Python→LangGraph→CrewAI→AutoGen四阶进阶路线

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 12:38:45

Java字符流处理:Reader类原理与应用实战

1. Java字符流处理基石&#xff1a;Reader类深度解析作为Java I/O体系中处理字符输入的核心抽象类&#xff0c;Reader在文本处理、文件读取、网络通信等场景中扮演着关键角色。不同于处理字节流的InputStream&#xff0c;Reader专门针对字符数据设计&#xff0c;自动处理字符编…

作者头像 李华