如何继承 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之后,就可以直接访问基类上定义的属性,包括linear、selectedIndex和steps。模板里还会用到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>←</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>→</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对这一模式的行为定义是:用户必须完成前面的步骤才能继续。那么「完成」由谁判断?文档给出两种机制,且二者可以共存,若completed和stepControl同时设置,stepControl优先。
方式一:不依赖表单,用 completed 属性
指南中的无表单示例:每个步骤设editable="false",并把[completed]绑定到一个外部状态。在完成之前(completed为false),用户无法进入下一步;点击「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。你也可以像方式一那样显式赋值来覆盖默认行为。
验证行为
完成组装后,按文档定义的行为核对以下现象:
- 普通模式:点击各步骤按钮或左右导航按钮,头部的
Step {n}/{总数}随之变化,当前步骤内容通过ngTemplateOutlet切换显示;左右导航受cdkStepperNext/cdkStepperPrevious指令驱动,无需手动处理。 - linear 模式 + completed:在
completed为false时,点击下一步无法前进(文档原话:该属性「won't allow the user to continue until it becomes true」);点击「Complete step」后再点下一步才前进。 - linear 模式 + stepControl:表单必填项未填时步骤无效,线性模式不放行;填写有效后前进。
- 越界赋值:手动给
selectedIndex赋越界值会抛出cdkStepper: Cannot assign out-of-bounds value to+ 'selectedIndex' +.,可用于确认自己的赋值逻辑没有算错下标。 - 重置:需要把 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(
linear、selectedIndex、selected、next、previous、reset)
如果后续需要带 Material Design 样式的标准 Stepper 而不是完全自定样式,可以对照仓库中的MatStepper文档与示例;本文路径只覆盖 CDK 层的自定义场景。
【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考