angularserver-side-renderingangular-ssrserver-renderingngx-toastr

How to get ngx-toastr to close in angular 17.3.8 (standalone) with SSR


ngx-toastr Will not close in angular 17.3.8 (standalone) with SSR

Steps to reproduce:

Create Application with Angular CLI version 17.3.8

npx @angular/cli@17.3.8 new ng-toaster
? Which stylesheet format would you like to use? Sass (SCSS)     [ https://sass-lang.com/documentation/syntax#scss]
? Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? Yes
√ Packages installed successfully.

Install ngx-toastr

npm install ngx-toastr --save

Installs ("ngx-toastr": "^19.0.0",)

Update src\app\app.config.ts

import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';

import { provideClientHydration } from '@angular/platform-browser';
import { routes } from './app.routes';

import { provideAnimations } from '@angular/platform-browser/animations';
import { provideToastr } from 'ngx-toastr';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes), 
    provideClientHydration(),
    provideAnimations(),
    provideToastr({
      timeOut: 2000,
      progressBar: true,
      progressAnimation: 'decreasing',
      closeButton: true
    })
  ]
};

Update src\styles.scss

@import 'ngx-toastr/toastr';

Update src\app\app.component.ts

import { Component, inject } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { ToastrService } from 'ngx-toastr';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss'
})
export class AppComponent {
  title = 'ng-toaster';
  private readonly _toasterService = inject(ToastrService);

  constructor() {
    this._toasterService.success('Close already!');
  }

  clear(){
    console.log(`Clear`);
    this._toasterService.clear();
  }
}

Update src\app\app.component.html

<h1>{{title}}</h1>
<p>Toaster please close</p>
<button type="button" (click)="clear()">Clear</button>
<router-outlet />

Run

npm run start

I tried setting the timeOut both on the provideToastr() but also in the AppComponent (this._toasterService.success) but it will not work. Clearing messages will also not work with this._toasterService.clear();


Solution

  • The toaster is also getting opened in SSR, which is causing this weird bug.

    You can create a toaster wrapper service, that opens the popup when you are only on the browser. For this we use isPlatformBrowser.

    import { isPlatformBrowser } from '@angular/common';
    import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
    import { ActiveToast, ToastrService } from 'ngx-toastr';
    
    @Injectable({
      providedIn: 'root',
    })
    export class ToasterManagerService {
      isBrowser = false;
      constructor(
        private readonly _toasterService: ToastrService,
        @Inject(PLATFORM_ID) private platformId: Object
      ) {
        this.isBrowser = isPlatformBrowser(platformId);
      }
    
      success(message: string) {
        if (!this.isBrowser) return;
        return this._toasterService.success(message);
      }
    
      clear(toastId: number) {
        if (!this.isBrowser) return;
        this._toasterService.clear(toastId);
      }
    }
    

    Then we can call the methods from this service on the component and it should start working.

    import { afterNextRender, Component, inject, Optional } from '@angular/core';
    import { RouterOutlet } from '@angular/router';
    import { ActiveToast, ToastrModule, ToastrService } from 'ngx-toastr';
    import { ToasterManagerService } from './toaster-service.service';
    
    @Component({
      selector: 'app-root',
      standalone: true,
      imports: [RouterOutlet, ToastrModule],
      templateUrl: './app.component.html',
      styleUrl: './app.component.scss',
    })
    export class AppComponent {
      title = 'ng-toaster';
      private readonly toasterManager = inject(ToasterManagerService);
      toastId!: number;
      constructor() {
        const toast = this.toasterManager.success('Close already!') || null;
        if (toast) {
          this.toastId = toast.toastId;
        }
      }
    
      clear() {
        console.log(`Clear`);
        this.toasterManager.clear(this.toastId);
      }
    }
    

    Stackblitz Demo -> cd test -> npm i -> npm run start