
Angular CDK Stepper 源码深度解析构建可复用的向导式分步工作流【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/componentsCDK stepper 是 Angular CDK 为分步流程Stepper提供的无样式基础层foundation它把当前激活哪一步、如何在步骤间前进/回退、如何校验线性流程等核心逻辑沉淀为可复用的类供CdkStepper的衍生实现如 Angular Material stepper继承与扩展。本文以 CDK stepper 文档 为骨架深入 stepper.ts 等源码实现系统讲解线性/非线性流程、表单校验、步骤状态、按钮指令、键盘交互与无障碍实现读完即可在自定义组件中落地一套完整的分步工作流。CDK Stepper 的设计定位逻辑与样式解耦在 Angular Material 组件体系中CDK 层与 Material 层的分工非常清晰。CDK stepper 只负责行为逻辑不负责任何视觉呈现CDK 层本文主题管理当前激活的是哪一步、处理键盘交互、暴露前进/回退的 API以及线性流程的校验规则Material 层在 CDK 的基础上增加 Material Design 样式与额外能力图标覆盖、动画、国际化标签等其实现位于 src/material/stepper/stepper.ts。CDK 层的核心类全部集中在 src/cdk/stepper/stepper.ts声明选择器职责CdkStepper源码[cdkStepper]分步流程的驱动器管理选中步骤、线性校验、键盘焦点CdkStep源码cdk-step单个步骤持有表单控制引用、可选/可编辑/已完成状态CdkStepLabel源码[cdkStepLabel]自定义步骤标签模板CdkStepHeader源码[cdkStepHeader]步骤头可聚焦自动赋予roletabCdkStepperNext/CdkStepperPrevious源码button[cdkStepperNext]/button[cdkStepperPrevious]前进/后退导航按钮所有公开 API 统一从 public-api.ts 导出模块封装为CdkStepperModule见 stepper-module.ts使用时导入该模块即可。CdkStepper 捕获的行为CDK 版本的基础 stepper主要管理哪一步是激活的包括处理键盘交互左/右方向键移动焦点Enter/Space 选中暴露前进next()、回退previous()与重置reset()的编程式 API维护选中步骤索引与选中步骤实例并发出变更事件。从源码看CdkStepper提供了如下核心输入/输出stepper.tslinear: boolean——是否为线性流程selectedIndex: number——当前选中步骤的索引双向绑定配合selectedIndexChangeselected: CdkStep——当前选中的步骤实例orientation: horizontal | vertical——步骤方向水平/垂直切换时同步更新键盘焦点管理器的垂直方向selectionChange: EventEmitterStepperSelectionEvent——选中步骤变化事件。其中StepperSelectionEventstepper.ts携带了selectedIndex、previouslySelectedIndex、selectedStep、previouslySelectedStep四个字段可供订阅方精确获知从哪一步切到了哪一步。next()与previous()的实现非常简单直观通过Math.min/Math.max将索引夹取在合法范围内stepper.ts/** Selects and focuses the next step in list. */ next(): void { this.selectedIndex Math.min(this._selectedIndex() 1, this.steps.length - 1); } /** Selects and focuses the previous step in list. */ previous(): void { this.selectedIndex Math.max(this._selectedIndex() - 1, 0); }线性 stepperLinear Stepper标记为linear的 stepper 要求用户先完成前面的步骤才能继续往后走。对于每一步都可以通过stepControl属性传入该步骤的顶层AbstractControl用于校验该步骤是否有效。有两种典型的组织方式整个 stepper 使用单个表单每个步骤各用一个表单。如果不想使用 Angular forms也可以给每个步骤传入completed属性——在它变成true之前用户无法继续。注意如果同时设置了completed和stepControlstepControl优先。从源码可以印证这一点。CdkStep.completed的 getterstepper.ts逻辑为get completed(): boolean { const override this._completedOverride(); const interacted this._interacted(); if (override ! null) { return override; // 显式设置的 completed 优先 } return interacted (!this.stepControl || isValid(this.stepControl)); }即默认行为是用户已与该步骤交互且没有 stepControl 或 stepControl 有效才视为已完成一旦手动设置completed则完全以手动值为准。线性校验的真正执行点在CdkStepper._anyControlsInvalidOrPendingstepper.ts当目标索引为线性流程中的后续步骤时它会检查该步骤之前的每一个步骤——若某个前置步骤未交互、控件无效或 pending且该步骤既非optional也没有显式completed覆盖则阻止跳转private _anyControlsInvalidOrPending(index: number): boolean { if (this.linear index 0) { return this.steps .toArray() .slice(0, index) .some(step { const control step.stepControl; const isIncomplete control ? isInvalid(control) || isPending(control) || !step.interacted : !step.completed; return isIncomplete !step.optional !step._completedOverride(); }); } return false; }此外isNavigablestepper.ts计算属性决定某一步是否可被导航到已完成、当前选中或非线性 stepper 中的任意步骤均可导航。使用单个表单当整个 stepper 共用一个表单时步骤中出现的下一步/上一步中间按钮必须显式设置typebutton防止在所有步骤完成前误触发表单提交。这是因为CdkStepperNext的默认type就是submit见下文Stepper buttons章节与 Material 文档中的单表单示例src/material/stepper/stepper.md一致form [formGroup]formGroup mat-stepper formArrayNameformArray linear mat-step formGroupName0 [stepControl]formArray.get([0]) ... div button matButton matStepperNext typebuttonNext/button /div /mat-step mat-step formGroupName1 [stepControl]formArray.get([1]) ... div button matButton matStepperPrevious typebuttonBack/button button matButton matStepperNext typebuttonNext/button /div /mat-step ... /mat-stepper /form每个步骤单独一个表单当每个步骤使用独立表单时流程的推进依赖其中某个表单被提交示例src/material/stepper/stepper.mdmat-stepper orientationvertical linear mat-step [stepControl]formGroup1 form [formGroup]formGroup1 ... /form /mat-step mat-step [stepControl]formGroup2 form [formGroup]formGroup2 ... /form /mat-step /mat-stepper值得一提的源码细节CdkStep通过ContentChildren(ControlContainer, {descendants: true})stepper.ts查找投影进步骤内部的表单——由于NgForm与FormGroupDirective都把自己提供为ControlContainerCDK 无需持有对两者的具体引用即可统一识别步骤内表单供重置时调用。使用 Signal Forms除了传统的模板驱动/响应式表单CdkStep.stepControl的类型被定义为StepControl AbstractControl | Fieldunknownstepper.ts也就是说也可以直接传入 Angular Signal Forms 的 field。源码中通过isField判断typeof value function并分别用control().valid()/control().invalid()/control().pending()做校验reset()时调用control().reset()stepper.ts。Material 层对应的示例见 stepper-signal-forms 示例 目录。步骤类型Types of StepsCdkStep提供三个与步骤类型相关的布尔输入全部使用booleanAttribute变换字符串false也会被正确解析为布尔false输入默认值语义optionalstepper.tsfalse在线性 stepper 中标记该步骤为可选未完成也可继续editablestepper.tstrue是否允许用户返回已完成的步骤修改答案editablefalse关闭completedstepper.ts动态步骤是否已完成见上文 getter 逻辑Optional step可选步骤如果线性 stepper 中某一步的完成不是必需的可在CdkStep上设置optional属性。回到线性校验源码_anyControlsInvalidOrPending中对!step.optional的判断正是其底层实现——可选的步骤不会阻塞后续导航。Editable step可编辑步骤默认所有步骤都是可编辑的用户可以返回之前已完成的步骤并修改内容。设置editablefalse可改变这一默认行为。该输入同时驱动步骤指示器图标类型默认displayDefaultIndicatorType为 true时未选中且已完成的步骤显示编辑图标edit还是完成图标done取决于editablestepper.ts。Completed step已完成步骤默认情况下completed在步骤有效线性 stepper 场景且用户与之交互过时返回true。但用户可以通过手动设置completed覆盖默认行为——此时 getter 直接返回覆盖值_completedOverride不再参考交互状态与表单校验。Stepper buttons导航按钮指令CDK 提供两个用于步骤间导航的按钮指令CdkStepperNext与CdkStepperPrevious。放在某个步骤内部时它们会自动添加点击处理器分别推进或回退工作流。看 stepper-button.ts 的完整实现/** Button that moves to the next step in a stepper workflow. */ Directive({ selector: button[cdkStepperNext], host: { [type]: type, (click): _stepper.next(), }, }) export class CdkStepperNext { _stepper inject(CdkStepper); /** Type of the next button. Defaults to submit if not specified. */ Input() type: string submit; } /** Button that moves to the previous step in a stepper workflow. */ Directive({ selector: button[cdkStepperPrevious], host: { [type]: type, (click): _stepper.previous(), }, }) export class CdkStepperPrevious { _stepper inject(CdkStepper); /** Type of the previous button. Defaults to button if not specified. */ Input() type: string button; }两个要点值得注意点击处理直接调用_stepper.next()/_stepper.previous()即复用上文提到的那两个索引夹取方法默认type差异CdkStepperNext默认为submitCdkStepperPrevious默认为button。这正是单表单场景下必须把 Next 按钮手动设为typebutton的根源——否则点击 Next 会连带触发表单提交。Material 层的对应实现MatStepperNext/MatStepperPrevious位于 src/material/stepper/stepper-button.ts。重置 stepperResetting若想把 stepper 重置回初始状态可调用reset()方法。注意重置会连带调用底层表单控件的reset从而清空表单值。CdkStepper.reset()stepper.ts与CdkStep.reset()stepper.ts的分工如下// CdkStepper.reset() reset(): void { this._updateSelectedItemIndex(0); // 选中回第 0 步 this.steps.forEach(step step.reset()); this._stateChanged(); } // CdkStep.reset() reset(): void { this._interacted.set(false); if (this._completedOverride() ! null) { this._completedOverride.set(false); } if (this._customError() ! null) { this._customError.set(false); } if (this.stepControl) { this._childForms?.forEach(form form.resetForm?.()); reset(this.stepControl); } }源码揭示的细节步骤重置不仅清空interacted、completed覆盖与自定义错误还会遍历步骤内部投影的表单调用resetForm()并调用stepControl.reset()含 Signal Forms field 的control().reset()分支确保表单彻底回到初始状态。Material stepper 的概览示例中Reset 按钮正是通过stepper.reset()实现见 stepper-overview-example.html。键盘交互CDK stepper 内置的键盘交互如下表与文档一致键盘快捷键行为Left Arrow聚焦上一个步骤头Right Arrow聚焦下一个步骤头Enter选中当前聚焦的步骤Space选中当前聚焦的步骤源码层面这套交互由FocusKeyManager来自 CDK a11y驱动。在ngAfterViewInitstepper.ts中初始化this._keyManager new FocusKeyManagerFocusableOption(this._sortedHeaders) .withWrap() // 焦点到头尾时循环 .withHomeAndEnd() // Home/End 跳到首尾 .withVerticalOrientation(this._orientation vertical);方向键的左右语义还会跟随布局方向RTL/LTR自动翻转CdkStepper注入可选的Directionality订阅其变化后调用_keyManager.withHorizontalOrientation(direction)stepper.ts。Enter/Space选中逻辑在_onKeydownstepper.ts中实现当无修饰键按下且键码为SPACE或ENTER时将selectedIndex设为当前键盘焦点所在步骤头并preventDefault防止默认行为如按钮激活、表单提交if (manager?.activeItemIndex ! null !hasModifier (keyCode SPACE || keyCode ENTER)) { this.selectedIndex manager.activeItemIndex; event.preventDefault(); } else { manager?.setFocusOrigin(keyboard).onKeydown(event); }一个值得借鉴的实现细节由于步骤头可能定义在渲染步骤的ngFor之外如 Material stepperDOM 顺序与QueryList顺序未必一致源码在 stepper.ts 中用compareDocumentPosition按文档位置排序保证键盘导航顺序与视觉顺序一致。无障碍Accessibility除内置键盘支持外CDK stepper不施加任何额外的无障碍处理。在实现自己的组件时官方建议将 stepper 视为标签页视图tabbed view赋予roletablist可点击选中步骤的步骤头赋予roletab选中后展开的内容赋予roletabpanel步骤头还应带aria-selected属性反映其选中状态。CDK 已经帮你做了一部分CdkStepHeader指令在宿主上自动绑定了roletabstep-header.ts并实现了FocusableOption.focus()以支持键盘焦点管理。CdkStep也暴露了aria-label、aria-labelledby输入stepper.ts供步骤头无障碍标签使用。完整的无障碍实现可参考 Angular Material stepper它在 CDK 之上补齐了roletablist/roletabpanel、aria-selected自动同步、aria-label提示并建议小屏优先使用垂直 stepper 以避免横向滚动。全局配置STEPPER_GLOBAL_OPTIONS虽然 CDK 文档正文未展开但源码为 stepper 提供了全局配置入口STEPPER_GLOBAL_OPTIONS注入令牌stepper.ts支持两个选项export interface StepperOptions { /** 是否显示错误状态默认 false。 */ showError?: boolean; /** 是否显示默认指示器类型默认 true。 */ displayDefaultIndicatorType?: boolean; }showError: true时已交互但校验失败的未选中步骤会显示错误指示器与errorMessagedisplayDefaultIndicatorType: false时步骤状态图标允许被完全自定义配合步骤的state输入如number/edit/done/error见STEP_STATE常量与StepState类型stepper.ts。Material stepper 文档给出了标准用法src/material/stepper/stepper.mdbootstrapApplication(MyApp, { providers: [ { provide: STEPPER_GLOBAL_OPTIONS, useValue: { displayDefaultIndicatorType: false } } ] });CdkStep构造函数中通过inject(STEPPER_GLOBAL_OPTIONS, {optional: true})读取配置stepper.ts并在indicatorType计算属性中依据这些配置推导每个步骤的指示器图标。在自定义组件中继承 CdkStepperCDK stepper 的设计目标是作为更具体 stepper 变体的地基。如果你要构建一套自定义样式的分步组件标准做法是继承CdkStepper并在模板中自行渲染步骤头与内容Material 的MatStepper正是如此见 src/material/stepper/stepper.ts。继承时需要关注_stepsContentChildren(CdkStep, {descendants: true})会包含嵌套 stepper 中的步骤因此基类在ngAfterContentInit中通过step._stepper this过滤出属于当前 stepper 的步骤stepper.ts步骤头既可以是内容子节点也可以是视图子节点键盘管理器在ngAfterViewInit初始化以保证两者都已就绪stepper.ts选中索引变化时若焦点仍在 stepper 内部会调用setActiveItem把焦点同步到新步骤头避免焦点丢失stepper.ts_getStepLabelId/_getStepContentId基于注入的_IdGenerator生成唯一 ID前缀cdk-stepper-用于关联步骤头与内容的无障碍引用stepper.ts。小结CDK stepper 用约六百行代码stepper.ts把分步工作流中最棘手的部分——激活步骤管理、线性校验、键盘导航、表单重置、无障碍骨架——全部抽象出来并通过CdkStepperModule一键导入。无论你是想直接使用 Material stepper还是需要构建一套完全自定义的分步组件理解CdkStepper与CdkStep的输入输出契约、stepControl的校验优先级、CdkStepperNext/Previous的默认按钮类型以及STEPPER_GLOBAL_OPTIONS的全局配置都能帮助你写出更稳健、更易维护的向导式交互。延伸阅读完整的 Material 化实现与示例请参见 src/material/stepper/stepper.md 与 src/components-examples/material/stepper 下的可运行示例如 stepper-overview。【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考