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

Angular 8中如何保持SnackBar存活以展示长任务进度

实现思路:复用SnackBar实例实时更新进度

我懂你现在的需求——要让SnackBar一直挂在那儿,实时显示SignalR推过来的Excel导出进度,而不是每次进度更新都弹一个新的提示条对吧?之前用AngularJS的Toaster能做到,Angular Material的SnackBar其实也可以,核心就是保存SnackBar的实例,然后直接更新它的内容,而不是反复调用open。我给你两种实现思路,结合你的代码来改:


方法一:直接修改默认SnackBar的内容(快速实现)

这种方式不用额外写组件,直接操作默认SnackBar的DOM内容,适合简单场景:

1. 先保存SnackBar实例

在组件里定义一个变量来存储打开的SnackBar实例,避免每次新建:

private progressSnackBar: MatSnackBarRef<any> | null = null;

constructor( private _snackBar: MatSnackBar, private _ngZone: NgZone) {}

2. 初始化进度SnackBar

在触发Excel导出的时候(比如用户点击导出按钮)打开SnackBar,设置duration: 0让它不会自动关闭:

// 假设这是用户触发导出的方法
startExcelExport() {
  // 只在没有活跃的SnackBar时创建新实例
  if (!this.progressSnackBar) {
    this.progressSnackBar = this._snackBar.open('Excel导出中... 0%', '取消', {
      duration: 0, // 0表示无限时长,不会自动关闭
      verticalPosition: 'bottom',
      horizontalPosition: 'center'
    });

    // 处理用户点击"取消"的逻辑
    this.progressSnackBar.onAction().subscribe(() => {
      // 调用SignalR通知服务器取消导出任务
      this.hubConnection.invoke('CancelExport').catch(err => console.log(err));
      // 关闭SnackBar并清空实例
      this.progressSnackBar?.dismiss();
      this.progressSnackBar = null;
    });
  }

  // 通知服务器开始导出任务
  this.hubConnection.invoke('StartExcelExport').catch(err => console.log(err));
}

3. 接收SignalR进度并更新SnackBar

修改SignalR的进度监听方法,用NgZone包裹更新逻辑(因为SignalR回调在Angular变更检测之外,需要手动触发):

// 假设服务器端推送进度的方法名为UpdateExportProgress
this.hubConnection.on("UpdateExportProgress", (progress) => {
  this._ngZone.run(() => {
    if (this.progressSnackBar) {
      // 更新SnackBar的文本内容
      const messageElement = this.progressSnackBar.instance.snackBarContainer.message.nativeElement;
      messageElement.textContent = `Excel导出中... ${progress}%`;

      // 进度到100%时,提示完成并自动关闭
      if (progress === 100) {
        messageElement.textContent = 'Excel导出完成!';
        setTimeout(() => {
          this.progressSnackBar?.dismiss();
          this.progressSnackBar = null;
        }, 3000);
      }
    }
  });
});

方法二:自定义SnackBar组件(更优雅,推荐)

如果需要更复杂的UI(比如进度条),或者不想直接操作DOM,最好自定义一个SnackBar组件:

1. 创建自定义进度SnackBar组件

import { Component, Input } from '@angular/core';
import { MatSnackBarRef } from '@angular/material/snack-bar';

@Component({
  template: `
    <div class="progress-snackbar">
      <span>Excel导出中...</span>
      <mat-progress-bar mode="determinate" [value]="progress" style="margin: 0 8px; flex: 1;"></mat-progress-bar>
      <span>{{ progress }}%</span>
      <button mat-button (click)="onCancel()">取消</button>
    </div>
  `,
  styles: [`
    .progress-snackbar {
      display: flex;
      align-items: center;
      gap: 8px;
      width: 300px;
    }
  `]
})
export class ProgressSnackBarComponent {
  @Input() progress = 0;

  constructor(private snackBarRef: MatSnackBarRef<ProgressSnackBarComponent>) {}

  onCancel() {
    this.snackBarRef.dismissWithAction();
  }
}

2. 打开自定义SnackBar并更新进度

// 触发导出时打开自定义SnackBar
startExcelExport() {
  if (!this.progressSnackBar) {
    this.progressSnackBar = this._snackBar.openFromComponent(ProgressSnackBarComponent, {
      duration: 0,
      data: { progress: 0 }, // 初始进度
      verticalPosition: 'bottom'
    });

    // 处理取消逻辑
    this.progressSnackBar.onAction().subscribe(() => {
      this.hubConnection.invoke('CancelExport').catch(err => console.log(err));
      this.progressSnackBar?.dismiss();
      this.progressSnackBar = null;
    });
  }

  this.hubConnection.invoke('StartExcelExport').catch(err => console.log(err));
}

// 接收SignalR进度更新
this.hubConnection.on("UpdateExportProgress", (progress) => {
  this._ngZone.run(() => {
    if (this.progressSnackBar) {
      this.progressSnackBar.instance.progress = progress;

      if (progress === 100) {
        // 可以替换成完成提示,或者直接关闭
        setTimeout(() => {
          this.progressSnackBar?.dismiss();
          this.progressSnackBar = null;
          // 导出完成后可以再弹一个成功提示
          this._snackBar.open('Excel导出已完成', '关闭', { duration: 3000 });
        }, 1000);
      }
    }
  });
});

关键注意事项

  • NgZone的作用:SignalR的回调函数是在Angular的变更检测上下文之外执行的,所以必须用_ngZone.run()包裹更新逻辑,否则视图不会自动刷新。
  • 内存泄漏预防:在组件销毁时,记得关闭SnackBar和SignalR连接:
    ngOnDestroy() {
      this.progressSnackBar?.dismiss();
      this.hubConnection?.stop().catch(err => console.log(err));
    }
    
  • 异常处理:如果SignalR连接断开,或者导出任务失败,要及时关闭SnackBar并提示错误信息。

内容的提问来源于stack exchange,提问作者olufikayo olowoyo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 22:19:03