@ngneat/hot-toast and ngx-toastr are both popular libraries for displaying non-blocking notification popups in Angular applications. ngx-toastr is the legacy standard, known for its stability and extensive configuration options built around Angular modules. @ngneat/hot-toast is a modern alternative inspired by React's hot-toast, designed for standalone components, functional providers, and a simpler API surface. Both handle success, error, warning, and info states, but they differ significantly in setup, customization, and alignment with recent Angular versions.
Both @ngneat/hot-toast and ngx-toastr solve the same problem β showing temporary feedback messages to users without blocking their workflow. However, they reflect different eras of Angular development. ngx-toastr represents the classic Module-based approach, while @ngneat/hot-toast embraces the modern Standalone and Functional API style. Let's compare how they handle setup, usage, and customization.
@ngneat/hot-toast uses functional providers, which fits cleanly into modern app.config.ts files.
provideHotToastConfig().// app.config.ts
import { provideHotToastConfig } from '@ngneat/hot-toast';
export const appConfig: ApplicationConfig = {
providers: [
provideHotToastConfig({
position: 'top-center',
duration: 3000
})
]
};
ngx-toastr relies on traditional NgModules and requires BrowserAnimationsModule.
ToastrModule.forRoot() in your app module.// app.module.ts
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ToastrModule } from 'ngx-toastr';
@NgModule({
imports: [
BrowserAnimationsModule,
ToastrModule.forRoot({
positionClass: 'toast-top-center',
timeOut: 3000
})
]
})
export class AppModule {}
@ngneat/hot-toast uses a simple service object imported directly.
toast.success() anywhere in your code.// component.ts
import { toast } from '@ngneat/hot-toast';
function saveData() {
toast.success('Data saved successfully');
// Or with options
toast.error('Failed to save', { duration: 5000 });
}
ngx-toastr requires injecting the ToastrService into your class.
// component.ts
import { ToastrService } from 'ngx-toastr';
@Component({ ... })
export class MyComponent {
constructor(private toastr: ToastrService) {}
saveData() {
this.toastr.success('Data saved successfully');
// Or with options
this.toastr.error('Failed to save', 'Error', { timeOut: 5000 });
}
}
@ngneat/hot-toast allows passing Angular components directly to the toast.
// component.ts
import { toast } from '@ngneat/hot-toast';
import { CustomComponent } from './custom.component';
function showCustom() {
toast.show(CustomComponent, {
data: { message: 'Action required' },
duration: 10000
});
}
ngx-toastr typically uses TemplateRef or ComponentPortal.
// component.ts
import { ToastrService } from 'ngx-toastr';
import { TemplateRef } from '@angular/core';
@Component({ ... })
export class MyComponent {
constructor(private toastr: ToastrService) {}
showCustom(templateRef: TemplateRef<any>) {
this.toastr.show(templateRef, 'Custom Title', {
toastClass: 'custom-toast'
});
}
}
@ngneat/hot-toast handles positioning via config objects.
style option.// component.ts
import { toast } from '@ngneat/hot-toast';
toast.success('Moved to bottom', {
position: 'bottom-center',
style: { background: 'blue', color: 'white' }
});
ngx-toastr uses CSS classes for positioning.
// component.ts
import { ToastrService } from 'ngx-toastr';
this.toastr.success('Moved to bottom', '', {
positionClass: 'toast-bottom-center',
toastClass: 'blue-theme'
});
@ngneat/hot-toast has built-in loading state management.
// component.ts
import { toast } from '@ngneat/hot-toast';
async function loadData() {
const loadingId = toast.load('Loading data...');
try {
await api.fetch();
toast.success('Done', { id: loadingId });
} catch {
toast.error('Failed', { id: loadingId });
}
}
ngx-toastr does not have built-in loading state transitions.
// component.ts
import { ToastrService } from 'ngx-toastr';
async function loadData() {
const loading = this.toastr.info('Loading data...');
try {
await api.fetch();
this.toastr.remove(loading.toastId);
this.toastr.success('Done');
} catch {
this.toastr.remove(loading.toastId);
this.toastr.error('Failed');
}
}
While the implementation differs, both libraries share core goals and capabilities.
// @ngneat/hot-toast
toast.success('OK');
toast.error('Fail');
// ngx-toastr
this.toastr.success('OK');
this.toastr.error('Fail');
// @ngneat/hot-toast
toast.show('Message', { duration: 4000 });
// ngx-toastr
this.toastr.show('Message', '', { timeOut: 4000 });
// @ngneat/hot-toast
toast.error('Critical', { duration: 0 });
// ngx-toastr
this.toastr.error('Critical', '', { disableTimeOut: true });
// Both handle this internally via role="alert"
// No extra code needed for basic support
| Feature | @ngneat/hot-toast | ngx-toastr |
|---|---|---|
| Setup | π Functional providers | π§© NgModules |
| Usage | π¦ Imported service object | π Injected service |
| Custom UI | π¨ Pass Component class | πΌοΈ TemplateRef or Portal |
| Loading State | β Built-in resolve/update | β Manual remove/show |
| Styling | ποΈ Inline style options | π CSS Classes |
| Modern Angular | β Standalone ready | β οΈ Requires adaptation |
@ngneat/hot-toast is like a modern utility belt π β lightweight, easy to grab, and designed for speed. It removes boilerplate and fits naturally into new Angular projects. Choose this for greenfield development where you want less configuration and more code.
ngx-toastr is like a heavy-duty toolbox π§° β robust, familiar, and everywhere. It has been around for years and works reliably in complex enterprise apps. Choose this for maintaining existing systems or if your team prefers traditional dependency injection patterns.
Final Thought: Both libraries will get the job done, but @ngneat/hot-toast reduces friction for modern Angular developers. If you are starting fresh, the simpler API and standalone support make it the logical choice β unless you are already locked into the ngx-toastr ecosystem.
Choose ngx-toastr if you are maintaining a legacy Angular application or need a battle-tested solution with a massive existing ecosystem. It is suitable for projects already using NgModules where migrating to a new notification system introduces unnecessary risk. It remains a solid choice for stable, long-term projects that do not require the latest Angular features.
Choose @ngneat/hot-toast if you are building a new Angular application using standalone components or recent versions (15+). It offers a simpler, function-based API that aligns with modern Angular practices like functional providers and signals. It is ideal for teams wanting less boilerplate and easier custom component integration without dealing with complex module imports.
DEMO: https://ngx-toastr.vercel.app
ViewContainerRef@for. Fewer dirty checks and higher performance@angular/animationsLatest version available for each version of Angular
| ngx-toastr | Angular |
|---|---|
| 13.2.1 | 10.x 11.x |
| 14.3.0 | 12.x 13.x |
| 15.2.2 | 14.x. |
| 16.2.0 | 15.x |
| 17.0.2 | 16.x |
| 18.x 19x | >= 17.x < 23.x |
| current | >= 20.x |
npm install ngx-toastr --save
step 1: add css
// regular style toast
@import 'ngx-toastr/toastr';
// bootstrap style toast
// or import a bootstrap 4 alert styled design (SASS ONLY)
// should be after your bootstrap imports, it uses bs4 variables, mixins, functions
@import 'ngx-toastr/toastr-bs4-alert';
// if you'd like to use it without importing all of bootstrap it requires
@import 'bootstrap/scss/functions';
@import 'bootstrap/scss/variables';
@import 'bootstrap/scss/mixins';
// bootstrap 4
@import 'ngx-toastr/toastr-bs4-alert';
// boostrap 5
@import 'ngx-toastr/toastr-bs5-alert';
"styles": [
"styles.scss",
"node_modules/ngx-toastr/toastr.css" // try adding '../' if you're using angular cli before 6
]
step 2: add ToastrModule to app NgModule, or provideToastr to providers.
import { ToastrModule } from 'ngx-toastr';
@NgModule({
imports: [
ToastrModule.forRoot(), // ToastrModule added
],
bootstrap: [App],
declarations: [App],
})
class MainModule {}
import { AppComponent } from './src/app.component';
import { provideToastr } from 'ngx-toastr';
bootstrapApplication(AppComponent, {
providers: [
provideToastr(), // Toastr providers
]
});
import { ToastrService } from 'ngx-toastr';
import { inject } from '@angular/core';
@Component({...})
export class YourComponent {
toastr = inject(ToastrService);
showSuccess() {
this.toastr.success('Hello world!', 'Toastr fun!');
}
}
There are individual options and global options.
Passed to ToastrService.success/error/warning/info/show()
| Option | Type | Default | Description |
|---|---|---|---|
| toastComponent | Component | Toast | Angular component that will be used |
| closeButton | boolean | false | Show close button |
| timeOut | number | 5000 | Time to live in milliseconds |
| extendedTimeOut | number | 1000 | Time to close after a user hovers over toast |
| disableTimeOut | boolean | 'timeOut' | 'extendedTimeOut' | false | Disable both timeOut and extendedTimeOut when set to true. Allows specifying which timeOut to disable, either: timeOut or extendedTimeOut |
| easing | string | 'ease-in' | Toast component easing |
| easeTime | string | number | 300 | Time spent easing |
| enableHtml | boolean | false | Allow html in message |
| newestOnTop | boolean | true | New toast placement |
| progressBar | boolean | false | Show progress bar |
| progressAnimation | 'decreasing' | 'increasing' | 'decreasing' | Changes the animation of the progress bar. |
| toastClass | string | 'ngx-toastr' | CSS class(es) for toast |
| positionClass | string | 'toast-top-right' | CSS class(es) for toast container |
| titleClass | string | 'toast-title' | CSS class(es) for inside toast on title |
| messageClass | string | 'toast-message' | CSS class(es) for inside toast on message |
| tapToDismiss | boolean | true | Close on click |
| onActivateTick | boolean | false | Fires changeDetectorRef.detectChanges() when activated. Helps show toast from asynchronous events outside of Angular's change detection |
success, error, info, warning take (message, title, ToastConfig) pass an
options object to replace any default option.
this.toastrService.error('everything is broken', 'Major Error', {
timeOut: 3000,
});
All individual options can be overridden in the global options to affect all toasts. In addition, global options include the following options:
| Option | Type | Default | Description |
|---|---|---|---|
| maxOpened | number | 0 | Max toasts opened. Toasts will be queued. 0 is unlimited |
| autoDismiss | boolean | false | Dismiss current toast when max is reached |
| iconClasses | object | see below | Classes used on toastr service methods |
| preventDuplicates | boolean | false | Block duplicate messages |
| countDuplicates | boolean | false | Displays a duplicates counter (preventDuplicates must be true). Toast must have a title and duplicate message |
| resetTimeoutOnDuplicate | boolean | false | Reset toast timeout on duplicate (preventDuplicates must be true) |
| includeTitleDuplicates | boolean | false | Include the title of a toast when checking for duplicates (by default only message is compared) |
iconClasses = {
error: 'toast-error',
info: 'toast-info',
success: 'toast-success',
warning: 'toast-warning',
};
Pass values to ToastrModule.forRoot() or provideToastr() to set global options.
// root app NgModule
imports: [
ToastrModule.forRoot({
timeOut: 10000,
positionClass: 'toast-bottom-right',
preventDuplicates: true,
}),
],
import { AppComponent } from './src/app.component';
import { provideToastr } from 'ngx-toastr';
bootstrapApplication(AppComponent, {
providers: [
provideToastr({
timeOut: 10000,
positionClass: 'toast-bottom-right',
preventDuplicates: true,
}),
]
});
export interface ActiveToast {
/** Your Toast ID. Use this to close it individually */
toastId: number;
/** the title of your toast. Stored to prevent duplicates if includeTitleDuplicates set */
title: string;
/** the message of your toast. Stored to prevent duplicates */
message: string;
/** a reference to the component see portal.ts */
portal: ComponentRef<any>;
/** a reference to your toast */
toastRef: ToastRef<any>;
/** triggered when toast is active */
onShown: Observable<any>;
/** triggered when toast is destroyed */
onHidden: Observable<any>;
/** triggered on toast click */
onTap: Observable<any>;
/** available for your use in custom toast */
onAction: Observable<any>;
}
Put toasts in a specific div inside your application. This should probably be
somewhere that doesn't get deleted. Add ToastContainerDirective to the ngModule
where you need the directive available. Make sure that your container has
an aria-live="polite" attribute, so that any time a toast is injected into
the container it is announced by screen readers.
import { NgModule } from '@angular/core';
import { ToastrModule, ToastContainerDirective } from 'ngx-toastr';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [
ToastrModule.forRoot({ positionClass: 'inline' }),
ToastContainerDirective,
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
Add a div with toastContainer directive on it.
import { Component, OnInit, viewChild, inject } from '@angular/core';
import { ToastContainerDirective, ToastrService } from 'ngx-toastr';
@Component({
selector: 'app-root',
template: `
<h1><a (click)="onClick()">Click</a></h1>
<div aria-live="polite" toastContainer></div>
`,
})
export class AppComponent implements OnInit {
toastContainer = viewChild(ToastContainerDirective, { static: true });
toastrService = inject(ToastrService);
ngOnInit() {
this.toastrService.overlayContainer = this.toastContainer;
}
onClick() {
this.toastrService.success('in div');
}
}
Remove all or a single toast by optional id
toastrService.clear(toastId?: number);
Remove and destroy a single toast by id
toastrService.remove(toastId: number);
If you are using SystemJS, you should also adjust your configuration to point to the UMD bundle.
In your SystemJS config file, map needs to tell the System loader where to
look for ngx-toastr:
map: {
'ngx-toastr': 'node_modules/ngx-toastr/bundles/ngx-toastr.umd.min.js',
}
If you do not want animations you can override the default
toast component in the global config to use
ToastNoAnimation instead of the default one.
In your main module (ex: app.module.ts)
import { ToastrModule, ToastNoAnimation, ToastNoAnimationModule } from 'ngx-toastr';
@NgModule({
imports: [
// ...
ToastNoAnimationModule.forRoot(),
],
// ...
})
class AppModule {}
That's it! No animations.
Create your toast component extending Toast see the demo's pink toast for an example https://github.com/scttcper/ngx-toastr/blob/master/src/app/pink.toast.ts
import { ToastrModule } from 'ngx-toastr';
@NgModule({
imports: [
ToastrModule.forRoot({
toastComponent: YourToastComponent, // added custom toast!
}),
],
bootstrap: [App],
declarations: [App, YourToastComponent], // add!
})
class AppModule {}
ngOnInit() {
setTimeout(() => this.toastr.success('sup'))
}
showToaster() {
this.toastr.success('Hello world!', 'Toastr fun!')
.onTap
.pipe(take(1))
.subscribe(() => this.toasterClickedHandler());
}
toasterClickedHandler() {
console.log('Toastr clicked');
}
toastClass: 'yourclass ngx-toastr'
See: https://github.com/scttcper/ngx-toastr/issues/594.toastr original toastr
angular-toastr AngularJS toastr
notyf notyf (css)
MIT