← Back to Demo Home

đŸ…°ī¸ ModernToasts + Angular

Complete Angular integration with services and dependency injection

🚀 Live Demo

Try the Angular integration in action:

đŸ“Ļ Installation

npm install modern-toasts

đŸŽ¯ Basic Usage

Simple Import and Use

import toast from 'modern-toasts';

@Component({
  selector: 'app-example',
  template: `
    <button (click)="showToast()">Show Toast</button>
  `
})
export class ExampleComponent {
  showToast() {
    toast.success('Hello from Angular!');
  }
}

🔧 Toast Service

Create an Angular service

// services/toast.service.ts
import { Injectable } from '@angular/core';
import toast from 'modern-toasts';

@Injectable({
  providedIn: 'root'
})
export class ToastService {
  
  constructor() {
    // Configure toast on service initialization
    toast.configure({
      position: 'bottom-right',
      maxVisibleStackToasts: 3,
      enableBorderAnimation: true,
      enableFillAnimation: true,
      pauseBackgroundToastsOnHover: true
    });
  }

  success(message: string, options?: any): string {
    return toast.success(message, options);
  }

  error(message: string, options?: any): string {
    return toast.error(message, options);
  }

  info(message: string, options?: any): string {
    return toast.info(message, options);
  }

  warning(message: string, options?: any): string {
    return toast.warning(message, options);
  }

  dismiss(id: string): void {
    toast.dismiss(id);
  }

  dismissAll(): void {
    toast.dismissAll();
  }

  configure(config: any): void {
    toast.configure(config);
  }

  // Event listeners
  on(event: string, callback: Function): void {
    toast.on(event, callback);
  }

  off(event: string, callback: Function): void {
    toast.off(event, callback);
  }
}

Use in components

import { Component } from '@angular/core';
import { ToastService } from './services/toast.service';

@Component({
  selector: 'app-user-profile',
  template: `
    <button (click)="saveProfile()">Save Profile</button>
  `
})
export class UserProfileComponent {
  
  constructor(private toastService: ToastService) {}

  async saveProfile() {
    try {
      await this.userService.saveProfile();
      this.toastService.success('Profile saved successfully!');
    } catch (error) {
      this.toastService.error('Failed to save profile');
    }
  }
}

🌐 HTTP Interceptor Integration

Global HTTP error handling

// interceptors/toast-error.interceptor.ts
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpErrorResponse } from '@angular/common/http';
import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';
import { ToastService } from '../services/toast.service';

@Injectable()
export class ToastErrorInterceptor implements HttpInterceptor {

  constructor(private toastService: ToastService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler) {
    return next.handle(req).pipe(
      catchError((error: HttpErrorResponse) => {
        let errorMessage = 'An error occurred';

        switch (error.status) {
          case 400:
            errorMessage = 'Bad request. Please check your input.';
            break;
          case 401:
            errorMessage = 'Unauthorized. Please log in again.';
            break;
          case 403:
            errorMessage = 'Access forbidden.';
            break;
          case 404:
            errorMessage = 'Resource not found.';
            break;
          case 500:
            errorMessage = 'Server error. Please try again later.';
            break;
          default:
            errorMessage = error.error?.message || 'Network error occurred.';
        }

        this.toastService.error(errorMessage, {
          autoDismiss: 5000
        });

        return throwError(error);
      })
    );
  }
}

Register the interceptor

// app.module.ts
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { ToastErrorInterceptor } from './interceptors/toast-error.interceptor';

@NgModule({
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: ToastErrorInterceptor,
      multi: true
    }
  ]
})
export class AppModule {}

📝 Form Validation Integration

Reactive forms with toast feedback

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ToastService } from './services/toast.service';

@Component({
  selector: 'app-contact-form',
  template: `
    <form [formGroup]="contactForm" (ngSubmit)="onSubmit()">
      <input formControlName="email" placeholder="Email">
      <textarea formControlName="message" placeholder="Message"></textarea>
      <button type="submit" [disabled]="contactForm.invalid">
        Submit
      </button>
    </form>
  `
})
export class ContactFormComponent {
  contactForm: FormGroup;

  constructor(
    private fb: FormBuilder,
    private toastService: ToastService
  ) {
    this.contactForm = this.fb.group({
      email: ['', [Validators.required, Validators.email]],
      message: ['', [Validators.required, Validators.minLength(10)]]
    });
  }

  onSubmit() {
    if (this.contactForm.invalid) {
      this.showValidationErrors();
      return;
    }

    this.submitForm();
  }

  private showValidationErrors() {
    const controls = this.contactForm.controls;

    if (controls['email'].errors?.['required']) {
      this.toastService.error('Email is required');
    } else if (controls['email'].errors?.['email']) {
      this.toastService.error('Please enter a valid email');
    }

    if (controls['message'].errors?.['required']) {
      this.toastService.error('Message is required');
    } else if (controls['message'].errors?.['minlength']) {
      this.toastService.error('Message must be at least 10 characters');
    }
  }

  private async submitForm() {
    try {
      await this.contactService.submit(this.contactForm.value);
      this.toastService.success('Message sent successfully!', {
        autoDismiss: 5000
      });
      this.contactForm.reset();
    } catch (error) {
      this.toastService.error('Failed to send message');
    }
  }
}

🔄 Real-world Examples

Data Service with Loading States

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { ToastService } from './toast.service';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  private loadingSubject = new BehaviorSubject<boolean>(false);
  public loading$ = this.loadingSubject.asObservable();

  constructor(
    private http: HttpClient,
    private toastService: ToastService
  ) {}

  async fetchData(): Promise<any> {
    this.loadingSubject.next(true);
    
    const loadingToastId = this.toastService.info('Loading data...', {
      autoDismiss: 0
    });

    try {
      const data = await this.http.get('/api/data').toPromise();
      
      this.toastService.dismiss(loadingToastId);
      this.toastService.success('Data loaded successfully!');
      
      return data;
    } catch (error) {
      this.toastService.dismiss(loadingToastId);
      throw error; // Let interceptor handle the error toast
    } finally {
      this.loadingSubject.next(false);
    }
  }
}

Route Guard with Toast Notifications

import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from './auth.service';
import { ToastService } from './toast.service';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {

  constructor(
    private authService: AuthService,
    private router: Router,
    private toastService: ToastService
  ) {}

  canActivate(): boolean {
    if (this.authService.isAuthenticated()) {
      return true;
    }

    this.toastService.warning('Please log in to access this page', {
      autoDismiss: 4000
    });
    
    this.router.navigate(['/login']);
    return false;
  }
}

🎭 Custom Toast Component

// components/toast-trigger.component.ts
import { Component, Input } from '@angular/core';
import { ToastService } from '../services/toast.service';

@Component({
  selector: 'app-toast-trigger',
  template: `
    <button 
      [class]="buttonClass" 
      (click)="triggerToast()"
      [disabled]="disabled"
    >
      {{ label }}
    </button>
  `
})
export class ToastTriggerComponent {
  @Input() type: 'success' | 'error' | 'info' | 'warning' = 'info';
  @Input() message: string = '';
  @Input() label: string = 'Show Toast';
  @Input() disabled: boolean = false;
  @Input() options: any = {};

  constructor(private toastService: ToastService) {}

  get buttonClass(): string {
    return `btn btn-${this.type}`;
  }

  triggerToast(): void {
    const toastMessage = this.message || `${this.type} toast triggered!`;
    this.toastService[this.type](toastMessage, this.options);
  }
}

Usage in templates

<app-toast-trigger
  type="success"
  message="Operation completed successfully!"
  label="Complete Task"
  [options]="{ autoDismiss: 3000 }">
</app-toast-trigger>

<app-toast-trigger
  type="error"
  message="Something went wrong!"
  label="Trigger Error">
</app-toast-trigger>

âš™ī¸ Advanced Configuration

// app.component.ts
import { Component, OnInit } from '@angular/core';
import { ToastService } from './services/toast.service';

@Component({
  selector: 'app-root',
  template: `<router-outlet></router-outlet>`
})
export class AppComponent implements OnInit {

  constructor(private toastService: ToastService) {}

  ngOnInit() {
    // Configure toast globally
    this.toastService.configure({
      position: 'top-right',
      maxVisibleStackToasts: 4,
      defaultDuration: 4000,
      enableBorderAnimation: true,
      enableFillAnimation: true,
      pauseBackgroundToastsOnHover: true,
      customCSS: `
        .toast-container {
          z-index: 9999;
        }
        .toast-angular {
          border-left: 4px solid #dd0031;
        }
      `
    });

    // Listen to toast events
    this.toastService.on('show', (toastData) => {
      console.log('Toast shown:', toastData);
    });
  }
}

📱 TypeScript Interfaces

// interfaces/toast.interface.ts
export interface CustomToastOptions {
  autoDismiss?: number;
  backgroundColor?: string;
  textColor?: string;
  borderColor?: string;
  showCloseButton?: boolean;
  pauseOnHover?: boolean;
  className?: string;
  icon?: string;
  animationDirection?: 'left-to-right' | 'right-to-left' | 'top-to-bottom' | 'bottom-to-top';
  userId?: string;
  component?: string;
}

export interface ToastResponse {
  id: string;
  success: boolean;
}

Typed service methods

// Enhanced service with types
import { CustomToastOptions, ToastResponse } from '../interfaces/toast.interface';

@Injectable({
  providedIn: 'root'
})
export class TypedToastService {
  
  success(message: string, options?: CustomToastOptions): string {
    return toast.success(message, options);
  }

  error(message: string, options?: CustomToastOptions): string {
    return toast.error(message, options);
  }

  // Async toast with promise
  async showWithConfirmation(
    message: string, 
    type: 'success' | 'error' | 'info' | 'warning' = 'info'
  ): Promise<ToastResponse> {
    const id = toast[type](message, { autoDismiss: 0 });
    
    return new Promise((resolve) => {
      const handleDismiss = () => {
        toast.off('dismiss', handleDismiss);
        resolve({ id, success: true });
      };
      
      toast.on('dismiss', handleDismiss);
    });
  }
}

🔗 Useful Links