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

如何检测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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 04:22:08