I’m working on an Angular application where I use ng-zorro-antd for displaying toast messages. I’ve encountered an issue where I want to display individual progress bars for each toast message, but currently, only the first toast message gets a progress bar, and when its time finishes, all the toast messages close.
Here’s my current implementation:
I have a ToastMessageService where I handle showing toast messages using NzNotificationService. Each toast message has a duration, and I’m using a single progress bar to show the progress for the first toast message.
My ToastMessageService code
import {Injectable, TemplateRef,} from "@angular/core";
import {NzNotificationService} from "ng-zorro-antd/notification";
import {Subject} from "rxjs";
@Injectable({providedIn: 'root'})
export class ToastMessageService {
templateRef!: TemplateRef<{}> | undefined;
showTimer: Subject<void> = new Subject<void>();
constructor(private message: NzNotificationService) {
}
public removeToast() {
this.message.remove();
}
public showMessageToast(message: string, toastType: 'success' | 'info' | 'warning' | 'error' | 'blank' = 'success',
options: any = {nzPlacement: 'top', nzDuration: 0}): void {
const toast: ToastProperties = this.createToastMessage(toastType);
const notificationRef = this.message.template(
this.templateRef!,
{
nzData: {message, toastType, iconClass: toast.iconClass},
nzClass: toast.className, ...options
}
);
this.showTimer.next()
}
public showErrorMessages(error: ProblemDetail | ValidationProblemDetail, showErrorToast: boolean = true, showErrorDuration: number = 0): string {
let errorMessage ;
const errors = (error as ValidationProblemDetail).errors;
if ((error as ValidationProblemDetail).errors && JSON.stringify(errors) !== '{}') {
errorMessage = this.handelErrors(error);
} else {
errorMessage = error.detail ;
}
if (showErrorToast) {
this.showMessageToast(errorMessage, 'error', {nzPlacement: 'top', nzDuration: showErrorDuration});
}
return errorMessage
}
private handelErrors(error: any): any {
return Object.keys(error.errors).map((key: any, PIndex) => {
const errors: any = error.errors;
errors[key] = errors[key].map((item: any, index: any) => `${(PIndex + index + 1)}- ${item}n`);
return errors[key].join(" ")
})
}
createToastMessage(toastType: 'success' | 'info' | 'warning' | 'error' | 'blank' = 'success'): ToastProperties {
let className: string = '';
let iconClass: string = '';
switch (toastType) {
case'success': {
className = 'border-bottom border-success';
iconClass = 'text-success';
break;
}
case 'error' : {
className = 'border-bottom border-danger';
iconClass = 'text-danger';
break;
}
case 'info' : {
className = 'border-bottom border-info text-end';
iconClass = 'text-info';
break;
}
case 'warning' : {
className = 'border-bottom border-warning';
iconClass = 'text-warning';
break;
}
case 'blank' : {
className = 'border-bottom border-dark';
iconClass = 'text-white';
break;
}
}
return {className, iconClass}
}
}
interface ProblemDetail {
type: string | undefined
title: string | undefined;
status: number | undefined;
detail: string | undefined;
instance: string | undefined;
[key: string]: any;
}
interface ValidationProblemDetail extends ProblemDetail {
errors: { [key: string]: string[]; } | undefined;
}
interface ToastProperties {
className: string,
iconClass: string
}
My TemplateComponent HTML code
<ng-template let-data="data">
<div class="antd-notification-body" id="{{data.id}}" (mouseover)="pauseProgress()" (mouseout)="resumeProgress()">
<span id="toast-message-text">
{{data.message}}
</span>
<div class="progress position-absolute bottom-0 start-0 end-0">
<div class="progress-bar" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
</ng-template>
My TemplateComponent TS code
export class TemplateComponentComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild(TemplateRef, {static: false}) template?: TemplateRef<{}>;
private timer: any;
_destroy = new Subject()
constructor(private toastMessageService: ToastMessageService,
private notification: NzNotificationService
) {
}
ngOnInit() {
this.toastMessageService.showTimer.pipe(takeUntil(this._destroy)).subscribe({
next: (res) => {
clearInterval(this.timer);
const progressBar = document.querySelector('.progress-bar') as HTMLElement;
if (progressBar) {
progressBar.style.width = `${0}%`;
}
this.show()
}
})
}
show() {
this.timer = setInterval(() => {
this.updateProgressBar();
}, 50);
}
ngAfterViewInit() {
this.toastMessageService.templateRef = this.template;
}
updateProgressBar(): void {
const progressBar = document.querySelector('.progress-bar') as HTMLElement;
if (progressBar) {
const width = parseInt(progressBar.style.width) || 0;
if (width < 100) {
progressBar.style.width = `${width + 1}%`;
} else {
clearInterval(this.timer);
this.notification.remove();
}
}
}
pauseProgress(): void {
clearInterval(this.timer);
}
resumeProgress(): void {
this.show()
}
ngOnDestroy() {
this._destroy.next(void 0);
this._destroy.complete()
}
}
I’ve tried to add individual progress bars for each toast message by modifying the template component, but couldn’t get it to work properly.
Could someone please guide me on how to achieve this? I want each toast message to have its own progress bar, and when its duration finishes, only that specific toast message should close, not all of them.
Any help or suggestions would be greatly appreciated!
Thank you!