Learn how to create a custom toaster module library in Angular 17+ using Angular CDK Portal. This step-by-step guide includes detailed instructions and examples to help you build flexible and reusable toaster notifications for your Angular applications.
In my journey as a developer working in administrative systems, I've often needed to implement a Toaster Notification System that provides non-intrusive feedback to end-users. This feedback can be anything from notifying the user that their changes were saved successfully or showing a validation/server error. Angular's Component Dev Kit (CDK) offers a powerful tool called Portal that simplifies the process of creating and displaying dynamic components. In this article, I'll walk you through how to create a Toaster Component in Angular 17+ using the @angular/cdk Portal.
Prerequisites
Before we begin, make sure you have Angular and @angular/cdk installed in your project. If not, you can install them using:
npm install @angular/core @angular/cdk
Step 1: Set Up the Toaster Component
First, let's generate our Toaster Component using Angular's CLI which will queue and display our notifications.
ng generate component toaster
This command will generate the necessary files for our Toaster Component. Let's modify the HTML and CSS for a basic toaster:
toaster.component.html
<div class="toaster-container">
<ng-template cdkPortalOutlet></ng-template>
</div>
You will notice here we are using the cdkPortalOutlet
directive which is used to add a portal outlet to our template.
toaster.component.css
Let's add some very basic CSS for our toaster to get started.
.toaster-container {
position: fixed;
top: 20px;
right: 20px;
width: 300px;
z-index: 1000;
}
Step 2: Create the Notification Component
Next, we need a component that will represent individual notifications. This component will be injected on demand inside the Portal we created earlier.
ng generate component notification
Modify the generated component to display a notification message:
notification.component.html
<div class="notification">
<p>{{ message }}</p>
</div>
notification.component.ts
import { Component, Input } from "@angular/core";
@Component({
selector: "app-notification",
templateUrl: "./notification.component.html",
styleUrls: ["./notification.component.css"],
})
export class NotificationComponent {
// Allow this component to accept an input called `message`
@Input() message: string = "";
}
notification.component.css
Let's customize our notification/message with some basic styling.
.notification {
background-color: #323232;
color: white;
padding: 10px;
margin-bottom: 10px;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
Step 3: Set Up the Portal Infrastructure
Now, we will set up the infrastructure to use portals in our Toaster Component.
toaster.component.ts
import { Component, ComponentRef, OnInit } from "@angular/core";
import { ComponentPortal, PortalInjector } from "@angular/cdk/portal";
import { Overlay, OverlayRef } from "@angular/cdk/overlay";
import { NotificationComponent } from "../notification/notification.component";
@Component({
selector: "app-toaster",
templateUrl: "./toaster.component.html",
styleUrls: ["./toaster.component.css"],
})
export class ToasterComponent implements OnInit {
private overlayRef: OverlayRef;
constructor(private overlay: Overlay) {}
ngOnInit(): void {
// Initialize an overlay and define a relative position
// Change this to control where your toaster should appear
this.overlayRef = this.overlay.create({
hasBackdrop: false,
positionStrategy: this.overlay
.position()
.global()
.top("20px")
.right("20px"),
});
}
showNotification(message: string): void {
// Create a Portal instance of our `NotificationComponent`
const notificationPortal = new ComponentPortal(NotificationComponent);
// Attach our `NotificationComponent` to the Overlay Portal
const notificationRef: ComponentRef<NotificationComponent> =
this.overlayRef.attach(notificationPortal);
// Now we can access the component instance
// and we can pass input for the `message` prop we created.
notificationRef.instance.message = message;
// Set a timeout to detach our component from the overlay
// after 3000 milliseconds
setTimeout(() => {
this.overlayRef.detach();
}, 3000);
}
}
Step 4: Use the Toaster Component
To use the Toaster Component, we'll integrate it into our main application component.
app.component.html
<!-- Make our toaster available globally -->
<app-toaster></app-toaster>
<!-- Add a button to test our toaster -->
<button (click)="showToast()">Show Toast</button>
app.component.ts
import { Component, ViewChild } from "@angular/core";
import { ToasterComponent } from "./toaster/toaster.component";
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"],
})
export class AppComponent {
@ViewChild(ToasterComponent) toaster!: ToasterComponent;
showToast(): void {
this.toaster.showNotification("This is a toast message!");
}
}
Step 5: Adjust Module Declarations
Make sure all components and CDK modules are declared and imported in your AppModule.
app.module.ts
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { AppComponent } from "./app.component";
import { ToasterComponent } from "./toaster/toaster.component";
import { NotificationComponent } from "./notification/notification.component";
import { OverlayModule } from "@angular/cdk/overlay";
import { PortalModule } from "@angular/cdk/portal";
@NgModule({
declarations: [AppComponent, ToasterComponent, NotificationComponent],
imports: [BrowserModule, OverlayModule, PortalModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
Conclusion
Using Angular's CDK Portal, we created a dynamic Toaster Component that can display notification messages. This method is not only flexible but also makes use of Angular's powerful CDK utilities to manage dynamic content efficiently. Now, you can expand this toaster to handle different types of notifications, styles, and durations based on your application's needs.