Skip to main content

Angular Push Notifications

Introduction

Push notifications are an essential feature of modern web applications that allow you to engage with your users even when they're not actively browsing your website. In Angular Progressive Web Applications (PWAs), push notifications enable you to send timely updates, alerts, and relevant information directly to your users' devices, helping to increase user engagement and retention.

In this tutorial, we'll explore how to implement push notifications in an Angular PWA using the Web Push API and Angular's Service Worker capabilities. By the end, you'll be able to create a fully functional push notification system that works across multiple platforms.

Prerequisites

Before you start implementing push notifications, you should have:

  • Basic knowledge of Angular
  • An existing Angular application or a new one
  • Angular Service Worker package installed
  • Understanding of asynchronous JavaScript (Promises, Observables)

Understanding Push Notifications Architecture

Push notifications in web applications involve three main components:

  1. Client Application (Angular PWA) - Requests permission from the user and registers with the push service
  2. Push Service - Provided by the browser vendor (like Firebase Cloud Messaging)
  3. Application Server - Your backend that sends push messages to the push service

The flow works like this:

  1. Your Angular app registers a service worker
  2. The app requests notification permission from the user
  3. If granted, the app subscribes to the push service and gets a subscription object
  4. This subscription is sent to your server
  5. Your server stores the subscription and uses it to send push messages
  6. When your server sends a message, the push service delivers it to the user's device
  7. The service worker receives the message and displays a notification

Let's implement this step by step.

Setting Up Your Angular PWA for Push Notifications

Step 1: Add Service Worker Support

If you haven't already set up your Angular app as a PWA, you need to add service worker support:

bash
ng add @angular/pwa

This command:

  • Adds the required packages
  • Creates a service worker configuration
  • Updates your angular.json file
  • Adds icons and web manifest

Step 2: Configure the Service Worker

The generated ngsw-config.json file configures your service worker. Make sure it includes:

json
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
/* ... existing config ... */
],
"dataGroups": [
/* ... existing config ... */
],
"navigationUrls": [
/* ... existing config ... */
]
}

Step 3: Create a Notification Service

Create a service to handle notification logic:

bash
ng generate service services/notification

This will create a new service file. Now, let's implement the notification functionality:

typescript
import { Injectable } from '@angular/core';
import { SwPush } from '@angular/service-worker';
import { HttpClient } from '@angular/common/http';
import { environment } from '../environments/environment';

@Injectable({
providedIn: 'root'
})
export class NotificationService {
readonly VAPID_PUBLIC_KEY = 'YOUR_VAPID_PUBLIC_KEY'; // You'll get this from your server

constructor(
private swPush: SwPush,
private http: HttpClient
) {}

// Request notification permission
requestPermission() {
if (!('Notification' in window)) {
console.log('This browser does not support notifications');
return Promise.resolve(false);
}

return Notification.requestPermission().then(permission => {
if (permission === 'granted') {
console.log('Notification permission granted');
return true;
} else {
console.log('Notification permission denied');
return false;
}
});
}

// Subscribe to push notifications
subscribeToNotifications() {
return this.requestPermission().then(granted => {
if (granted) {
this.swPush.requestSubscription({
serverPublicKey: this.VAPID_PUBLIC_KEY
})
.then(subscription => {
// Send subscription to server
return this.sendSubscriptionToServer(subscription);
})
.catch(error => {
console.error('Could not subscribe to notifications', error);
});
}
});
}

// Send subscription to server
private sendSubscriptionToServer(subscription: PushSubscription) {
return this.http.post(
`${environment.apiUrl}/notifications/subscribe`,
subscription.toJSON()
).toPromise();
}

// Unsubscribe from notifications
unsubscribeFromNotifications() {
this.swPush.unsubscribe()
.then(() => {
console.log('Unsubscribed from notifications');
})
.catch(error => {
console.error('Error unsubscribing', error);
});
}
}

Step 4: Listen for Push Notifications

Add a listener for push events in your app component or a dedicated service:

typescript
import { Component, OnInit } from '@angular/core';
import { SwPush } from '@angular/service-worker';
import { NotificationService } from './services/notification.service';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
notificationsEnabled = false;

constructor(
private swPush: SwPush,
private notificationService: NotificationService
) {}

ngOnInit() {
// Check if notifications are enabled
if (Notification.permission === 'granted') {
this.notificationsEnabled = true;
}

// Listen for push messages
this.swPush.messages.subscribe((message: any) => {
console.log('Received push message', message);
// Handle message in UI if needed
});

// Listen for notification clicks
this.swPush.notificationClicks.subscribe(({ action, notification }) => {
console.log('Notification clicked', action, notification);

// Example: navigate the user based on the notification data
if (notification.data && notification.data.url) {
window.open(notification.data.url);
}
});
}

enableNotifications() {
this.notificationService.subscribeToNotifications()
.then(() => {
this.notificationsEnabled = true;
});
}

disableNotifications() {
this.notificationService.unsubscribeFromNotifications();
this.notificationsEnabled = false;
}
}

Step 5: Create UI Components

Add buttons to your template for subscribing and unsubscribing from notifications:

html
<div class="notification-controls">
<button *ngIf="!notificationsEnabled" (click)="enableNotifications()" class="btn btn-primary">
Enable Notifications
</button>
<button *ngIf="notificationsEnabled" (click)="disableNotifications()" class="btn btn-secondary">
Disable Notifications
</button>
</div>

<div *ngIf="!notificationsEnabled" class="notification-prompt">
<p>Enable notifications to stay updated with the latest news and updates!</p>
</div>

Creating the Backend for Push Notifications

To complete the push notification system, you need a server that can:

  1. Store user subscriptions
  2. Send push messages to those subscriptions

Here's a simple example using Node.js and Express:

javascript
const express = require('express');
const webpush = require('web-push');
const bodyParser = require('body-parser');
const cors = require('cors');

const app = express();
app.use(bodyParser.json());
app.use(cors());

// VAPID keys should be generated only once
const vapidKeys = webpush.generateVAPIDKeys();

// Set VAPID details
webpush.setVapidDetails(
'mailto:[email protected]',
vapidKeys.publicKey,
vapidKeys.privateKey
);

// Store subscriptions (in a real app, use a database)
const subscriptions = [];

// Subscribe route
app.post('/api/notifications/subscribe', (req, res) => {
const subscription = req.body;

// Store the subscription
subscriptions.push(subscription);

// Send welcome notification
const payload = JSON.stringify({
title: 'Welcome to Push Notifications',
body: 'You have successfully subscribed to push notifications!',
icon: '/assets/icons/icon-128x128.png'
});

webpush.sendNotification(subscription, payload)
.catch(error => console.error(error));

res.status(201).json({ message: 'Subscription added successfully.' });
});

// Route to send a notification to all subscribers
app.post('/api/notifications/send', (req, res) => {
const notification = req.body;
const payload = JSON.stringify({
title: notification.title,
body: notification.body,
icon: notification.icon || '/assets/icons/icon-128x128.png',
data: notification.data || {}
});

const sendNotifications = subscriptions.map(subscription => {
return webpush.sendNotification(subscription, payload)
.catch(error => {
console.error('Error sending notification, reason: ', error);
// Remove failed subscriptions
if (error.statusCode === 410) {
const index = subscriptions.indexOf(subscription);
subscriptions.splice(index, 1);
}
});
});

Promise.all(sendNotifications)
.then(() => {
res.status(200).json({ message: 'Notifications sent successfully.' });
})
.catch(error => {
res.status(500).json({ error: 'Failed to send notifications.' });
});
});

const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
console.log('VAPID Public Key:', vapidKeys.publicKey);
});

Remember to install the required packages:

bash
npm install express web-push body-parser cors

Testing Your Push Notification System

Once you have set up both the frontend and backend, follow these steps to test:

  1. Update your NotificationService with the public VAPID key from your server
  2. Run your Angular app with ng serve
  3. Build and run your backend server
  4. Open your Angular app and click "Enable Notifications"
  5. You should see a browser prompt asking for notification permission
  6. After granting permission, you should receive a welcome notification
  7. Test sending notifications from your backend API

Handling Different Notification Scenarios

1. Scheduled Notifications

To implement scheduled notifications, store the notification and the time it should be sent on your server:

javascript
// On your server
const schedule = require('node-schedule');

function scheduleNotification(subscriptions, notificationData, datetime) {
schedule.scheduleJob(datetime, function() {
const payload = JSON.stringify({
title: notificationData.title,
body: notificationData.body,
icon: notificationData.icon || '/assets/icons/icon-128x128.png',
data: notificationData.data || {}
});

subscriptions.forEach(subscription => {
webpush.sendNotification(subscription, payload)
.catch(error => console.error(error));
});
});
}

2. Handling Offline Notifications

One of the benefits of PWAs is that notifications can be received even when the user is offline. The service worker handles this automatically when the user comes back online.

3. Rich Notifications

You can send more advanced notifications with buttons and actions:

javascript
const payload = JSON.stringify({
title: 'New Feature Available',
body: 'Check out our new dashboard visualization tools!',
icon: '/assets/icons/icon-128x128.png',
badge: '/assets/icons/badge-72x72.png',
image: '/assets/screenshots/feature-image.jpg',
actions: [
{ action: 'explore', title: 'Explore Now' },
{ action: 'dismiss', title: 'Later' }
],
data: {
url: 'https://yourapp.com/new-features'
}
});

Then handle the actions in your service worker:

javascript
self.addEventListener('notificationclick', event => {
const notification = event.notification;
notification.close();

if (event.action === 'explore') {
clients.openWindow(notification.data.url);
} else if (event.action === 'dismiss') {
// Do nothing or log that the notification was dismissed
} else {
// Default action when notification is clicked but no action button is clicked
clients.openWindow(notification.data.url);
}
});

Best Practices for Push Notifications

  1. Ask for permission at the right time - Don't ask for permission immediately when the app loads. Wait until the user has engaged with your app and understands the value of notifications.

  2. Be clear about what notifications will contain - Users are more likely to grant permission if they know what to expect.

  3. Don't abuse the privilege - Sending too many notifications can annoy users and cause them to block notifications or uninstall your app.

  4. Make notifications actionable - Include clear actions that users can take directly from the notification.

  5. Personalize notifications - Use user data to make notifications more relevant and engaging.

  6. Track notification metrics - Monitor open rates and conversions to improve your notification strategy.

Troubleshooting Common Issues

Notification Permission Denied

If a user denies notification permission, the browser remembers this choice, and you cannot programmatically request permission again. The user must manually change the permission in their browser settings.

Solution: Provide clear instructions to users on how to change notification settings in their browser.

Service Worker Not Registered

If your service worker isn't registering properly, check the browser's console for errors.

Solution: Make sure your service worker file is in the correct location and properly configured in your Angular app.

Push Subscription Failed

This can happen if the browser doesn't support push notifications or if there's an issue with your VAPID keys.

Solution: Implement proper feature detection and error handling for unsupported browsers.

Summary

In this tutorial, you've learned how to:

  1. Set up an Angular PWA for push notifications
  2. Create a notification service to handle permissions and subscriptions
  3. Build a backend to store subscriptions and send push messages
  4. Implement different types of notifications
  5. Follow best practices for user experience

Push notifications are a powerful way to re-engage users and keep them informed about important updates. By implementing them in your Angular PWA, you can create a more engaging and native-like experience for your users.

Additional Resources

Exercises

  1. Implement a notification preference center where users can choose which types of notifications they want to receive.
  2. Add support for rich media in notifications, such as images or action buttons.
  3. Implement analytics to track notification open rates and engagement.
  4. Create a notification queue system that prevents too many notifications from being sent at once.
  5. Add support for localized notifications based on the user's language preference.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)