Angular Material provides a fantastic default snackbar right out of the box. However, modern applications often require more than just basic string messages to effectively communicate with users. You might need custom styling, dynamic HTML injection, or even list support to neatly display a group of validation errors. By building a Custom Angular 22 Snackbar, you can achieve all of this while ensuring your application remains fully zoneless and perfectly safe for Server-Side Rendering (SSR). Let’s dive into how to architect this highly flexible UI service.
Handling SSR Safely for a Custom Angular 22 Snackbar

Opening a DOM-based overlay during Server-Side Rendering (SSR) will almost certainly cause fatal errors in your Node server. Because the Material overlay requires access to the document and window objects, we must explicitly check the platform environment before attempting to trigger it.
Since this customized overlay relies on these browser APIs to render, injecting a dedicated platform service is the cleanest way to ensure the code only fires in the browser context.
Read our complete guide to Angular SSR Best Practices
Here is the lightweight platform check service:
TypeScript
import { Service, inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
@Service()
export class PlatformService {
private platformId = inject(PLATFORM_ID);
get isBrowser(): boolean {
return isPlatformBrowser(this.platformId);
}
get isServer(): boolean {
return isPlatformServer(this.platformId);
}
}
The Core Service Logic of Our Custom Angular 22 Snackbar
Now that we have our platform checks in place, we can build the actual service wrapper. This service acts as a clean abstraction over MatSnackBar, injecting our custom data payload and handling the SSR check automatically.
By centralizing this logic, deploying this enhanced notification system anywhere in your application becomes a seamless one-liner. It takes the boilerplate out of your components while keeping your application architecture pristine.
Deep dive into Angular Dependency Injection patterns
TypeScript
import { Service, inject } from '@angular/core';
import { PlatformService } from './platform.service';
import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar';
import { SnackbarComponent } from '../../ui/snackbar/snackbar.component';
export type SnackbarType = 'success' | 'error' | 'warning' | 'info';
export interface SnackbarData {
type: SnackbarType;
message?: string;
html?: string;
list?: string[];
title?: string;
}
@Service()
export class SnackbarService {
private snackBar = inject(MatSnackBar);
private platformService = inject(PlatformService);
show(data: SnackbarData, config?: MatSnackBarConfig) {
if (!this.platformService.isBrowser) {
return; // SSR Safety!
}
this.snackBar.openFromComponent(SnackbarComponent, {
data,
duration: config?.duration ?? 5000,
panelClass: ['app-snackbar', `app-snackbar-${data.type}`],
horizontalPosition: config?.horizontalPosition ?? 'center',
verticalPosition: config?.verticalPosition ?? 'bottom',
...config
});
}
success(message: string, title?: string) {
this.show({ type: 'success', message, title });
}
// Extendable with error(), warning(), and info() methods...
}
Component Template using Modern Control Flow for a Custom Angular 22 Snackbar
Angular 22 provides powerful new @if and @for control blocks natively in the template. This makes conditional rendering faster, cleaner, and much more readable than the older *ngIf structural directives.
Thanks to these control flow blocks, it is incredibly easy to conditionally render optional titles, standard text messages, injected HTML, or even bulleted lists natively inside our dynamic component depending on the data payload passed to it.
HTML
<div class="snackbar-container">
<div class="snackbar-icon">
<mat-icon>{{ getIcon() }}</mat-icon>
</div>
<div class="snackbar-content">
@if (data.title) {
<div class="snackbar-title">{{ data.title }}</div>
}
@if (data.message) {
<div class="snackbar-message">{{ data.message }}</div>
}
@if (data.html) {
<div class="snackbar-html" [innerHTML]="data.html"></div>
}
@if (data.list && data.list.length > 0) {
<ul class="snackbar-list">
@for (item of data.list; track item) {
<li>{{ item }}</li>
}
</ul>
}
</div>
<div class="snackbar-actions">
<button mat-icon-button (click)="dismiss()">
<mat-icon>close</mat-icon>
</button>
</div>
</div>
Styling Without Encapsulation Restrictions
To properly style Material Design components from the ground up, you will often need to set encapsulation: ViewEncapsulation.None inside your component decorator. This allows you to easily override the default MDC surface styles that Material applies.
To prevent these global styles from leaking out and affecting the rest of your app, simply scope your CSS by targeting the panelClass we provided in the service (for example, using .app-snackbar-success & { background-color: #2e7d32; } in SCSS). By doing this, adding a Custom Angular 22 Snackbar layout becomes both safe and highly maintainable.
Conclusion
Building a Custom Angular 22 Snackbar gives you the ultimate flexibility to handle rich user notifications—like rendering safe HTML and structural lists—while ensuring your application remains strictly zoneless and SSR-safe. By effectively wrapping the default Angular Material component, you protect your server-side rendering pipelines and create a scalable UI solution that your entire development team will love using. Upgrade your notifications today and take full advantage of Angular’s modern feature set!
Learn how to NgRx with Angular 22
Official Documentation for Angular Material Snackbar

