如何检测mat-stepper验证失败并触发MatSnackBar提示
解决MatStepper切换步骤时验证失败无法触发提示的问题
我之前在项目里也遇到过完全一样的需求,以下几个方案都能有效解决这个问题,你可以根据自己的项目情况选择:
方案一:手动拦截步骤切换操作(最简单直接)
这个方案不需要修改组件库源码,只需要在调用stepper.next()/stepper.select()之前手动验证当前步骤的表单状态,灵活度很高:
代码示例(响应式表单)
import { Component } from '@angular/core'; import { MatStepper } from '@angular/material/stepper'; import { MatSnackBar } from '@angular/material/snack-bar'; import { FormGroup, FormControl, Validators } from '@angular/forms'; @Component({ selector: 'app-stepper-example', templateUrl: './stepper-example.component.html' }) export class StepperExampleComponent { firstFormGroup = new FormGroup({ firstName: new FormControl('', Validators.required), lastName: new FormControl('', Validators.required) }); secondFormGroup = new FormGroup({ email: new FormControl('', [Validators.required, Validators.email]) }); constructor(private snackBar: MatSnackBar) {} onNext(stepper: MatStepper) { const currentForm = stepper.selected.stepControl as FormGroup; if (currentForm && !currentForm.valid) { // 标记所有控件为已触碰,让表单显示内置错误提示 currentForm.markAllAsTouched(); // 显示SnackBar提示 this.snackBar.open('请填写当前步骤的所有必填字段', '关闭', { duration: 3000, verticalPosition: 'top' }); return; // 阻止执行下一步 } stepper.next(); } onSelectStep(stepper: MatStepper, targetIndex: number) { const currentForm = stepper.selected.stepControl as FormGroup; if (currentForm && !currentForm.valid) { currentForm.markAllAsTouched(); this.snackBar.open('请先完成当前步骤的必填项再切换', '关闭', { duration: 3000 }); return; } stepper.selectedIndex = targetIndex; } }
模板修改
把原来直接绑定stepper.next()的按钮,改成调用我们自定义的方法:
<mat-horizontal-stepper #stepper> <mat-step [stepControl]="firstFormGroup"> <form [formGroup]="firstFormGroup"> <!-- 表单控件 --> <button mat-button (click)="onNext(stepper)">下一步</button> </form> </mat-step> <mat-step [stepControl]="secondFormGroup"> <!-- 第二步内容 --> <button mat-button (click)="stepper.previous()">上一步</button> <button mat-button>完成</button> </mat-step> </mat-horizontal-stepper>
方案二:扩展MatStepper类(更优雅的全局解决方案)
如果你的项目中有多个stepper需要这个功能,自定义一个stepper组件会更高效,避免重复代码:
自定义Stepper组件
import { MatStepper } from '@angular/material/stepper'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Injector } from '@angular/core'; export class ValidatingStepper extends MatStepper { private snackBar: MatSnackBar; constructor(injector: Injector) { super(injector); this.snackBar = injector.get(MatSnackBar); } // 重写next方法,添加验证逻辑 override next(): void { if (this._anyControlsInvalidOrPending()) { this.showValidationError(); return; } super.next(); } // 重写select方法,拦截跨步骤切换 override select(step: any): void { // 只有当当前步骤未完成时才拦截 if (this.selected && this._anyControlsInvalidOrPending()) { this.showValidationError(); return; } super.select(step); } private showValidationError(): void { // 标记当前步骤的所有控件为已触碰 this.selected.stepControl?.markAllAsTouched(); // 显示提示 this.snackBar.open('请完成当前步骤的必填项', '知道了', { duration: 3000, verticalPosition: 'top' }); } }
在模块中注册自定义Stepper
import { NgModule } from '@angular/core'; import { ValidatingStepper } from './validating-stepper'; import { MatStepperModule } from '@angular/material/stepper'; @NgModule({ declarations: [ValidatingStepper], exports: [ValidatingStepper] }) export class ValidatingStepperModule {}
使用自定义Stepper
<app-validating-stepper #stepper> <!-- 步骤内容和原来一样 --> </app-validating-stepper>
方案三:监听Stepper的索引变化(适合已有大量代码的场景)
如果不想修改现有按钮的绑定逻辑,可以通过监听selectedIndexChange事件来拦截无效切换:
import { Component, ViewChild, AfterViewInit, OnDestroy } from '@angular/core'; import { MatStepper } from '@angular/material/stepper'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Subject } from 'rxjs'; import { takeUntil, tap } from 'rxjs/operators'; @Component({ selector: 'app-existing-stepper', templateUrl: './existing-stepper.component.html' }) export class ExistingStepperComponent implements AfterViewInit, OnDestroy { @ViewChild('stepper') stepper!: MatStepper; private destroy$ = new Subject<void>(); constructor(private snackBar: MatSnackBar) {} ngAfterViewInit() { this.stepper.selectedIndexChange.pipe( takeUntil(this.destroy$), tap((newIndex) => { const currentForm = this.stepper.selected?.stepControl; // 检查当前步骤是否无效,并且是向前切换/跨步骤切换 if (currentForm && !currentForm.valid && newIndex > this.stepper.selectedIndex) { // 恢复到原来的索引 this.stepper.selectedIndex = this.stepper.selectedIndex; currentForm.markAllAsTouched(); this.snackBar.open('请填写必填字段', '关闭', { duration: 3000 }); } }) ).subscribe(); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } }
注意:这个方案因为是在索引变化后恢复,可能会有轻微的视觉闪烁,不如前两个方案流畅。
内容的提问来源于stack exchange,提问作者wutzebaer
相关产品推荐
相关产品推荐

