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
相关产品推荐
相关产品推荐

