I have implemented a "snackbar service" that display a snackbar:
snackbar.service.ts
import { Subscription } from 'rxjs/Subscription';
import { Subject } from 'rxjs/Subject';
import { Inject, Injectable, OnDestroy } from '@angular/core';
import { MatSnackBar, MdSnackBarConfig } from '@angular/material/snack-bar';
import { MdSnackBarRef, SimpleSnackBar } from '@angular/material/snack-bar';
export class SnackBarMessage {
message: string;
action: string = null;
config: MdSnackBarConfig = null;
}
@Injectable()
export class SnackBarService implements OnDestroy
{
private messageQueue: Subject<SnackBarMessage> = new Subject<SnackBarMessage>();
private subscription: Subscription;
private snackBarRef: MdSnackBarRef<SimpleSnackBar>;
constructor(public snackBar: MatSnackBar){
this.subscription = this.messageQueue.subscribe(message => {
this.snackBarRef = this.snackBar.open(message.message, message.action, message.config);
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
/**
* Add a message
* @param message The message to show in the snackbar.
* @param action The label for the snackbar action.
* @param config Additional configuration options for the snackbar.
*/
add(message: string, action?: string, config?: MdSnackBarConfig): void{
if ( !config ){
config = new MdSnackBarConfig();
config.duration = 10000;
}
let sbMessage = new SnackBarMessage();
sbMessage.message = message;
sbMessage.action = action;
sbMessage.config = config;
this.messageQueue.next(sbMessage);
}
}
I want display multiple snackbars in sequence:
test.component.ts
import { Component } from '@angular/core';
import { SnackBarService } from 'app/core/services/snackbar.service';
@Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.scss']
})
export class TestComponent {
constructor(public snackBarService: SnackBarService) {
this.snackBarService.add('A');
this.snackBarService.add('B');
this.snackBarService.add('C');
}
}
But all message are displayed at same time (overlapping).
How can I wait for a snackBar afterDismissed for display a new message into messageQueue?
As @Aamir Khan pointed out - using afterDismissed
, I have tweaked your code a bit.
showNext() {
if (this.msgQueue === 0) {
return;
}
let message = this.msgQueue.shift();
this.isInstanceVisible = true;
this.snackBarRef = this.snackBar.open(message.message, message.action, {duration: 2000});
this.snackBarRef.afterDismissed().subscribe(() => {
this.isInstanceVisible = false;
this.showNext();
});
}
And inside add()
added this -
this.msgQueue.push(sbMessage);
if (!this.isInstanceVisible) {
this.showNext();
}
Plunker
Caution - Its kind of a dirty and non standard way, not an ideal user experience (IMO), above code might have some memory leaks and race conditions, due to usage of flags.
Here is my solution
import {Injectable, NgZone, OnDestroy} from '@angular/core';
import {MatSnackBar, MatSnackBarRef, TextOnlySnackBar} from '@angular/material/snack-bar';
import {BehaviorSubject, EMPTY} from 'rxjs';
import {concatMap} from 'rxjs/operators';
interface ToasterMessage {
type: ToasterMessageType;
message: string;
}
@Injectable({
providedIn: 'root'
})
export class SnackService implements OnDestroy {
private toastSteam: BehaviorSubject<ToasterMessage | {}> = new BehaviorSubject<ToasterMessage | {}>({});
private toastStream$ = this.toastSteam.asObservable();
constructor(private snackBar: MatSnackBar, private zone: NgZone) {
// for each toast message we map an observable, and we concat to wait for the previous
// to finish. We can use mergeMap or switchMap if we want other behavior
this.toastStream$.pipe(
concatMap((toast: ToasterMessage | {}) => {
if (!('type' in toast)) {
return EMPTY;
}
return this.handleToastMessage(toast).afterDismissed();
})
).subscribe((_) => {
});
}
openSnackBar(message: string,
action: string | undefined,
color: string = 'green-snackbar'): MatSnackBarRef<TextOnlySnackBar> {
return this.zone.run(() => {
const snackBarRef = this.snackBar.open(message, action, {
duration: 4000,
panelClass: color,
horizontalPosition: 'center',
});
snackBarRef.onAction().subscribe(() => snackBarRef.dismiss());
return snackBarRef;
});
}
// just a helper method to make code simpler
handleToastMessage(toast: ToasterMessage): MatSnackBarRef<TextOnlySnackBar> {
return this.openSnackBar(toast.message, 'x', `${toast.type}-snackbar`);
}
addToast(toast: ToasterMessage): void {
this.toastSteam.next(toast);
}
ngOnDestroy(): void {
this.toastSteam.next({});
this.toastSteam.complete();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With