In a previous tutorial I wrote about how to build a one-on-one chat using Angular and CometChat. This tutorial picks up where the last one ended and shows how to enable chat push notifications using the CometChat push notifications extension.

If you stumbled upon this tutorial first and wish to follow along from the very beginning - please do by starting here. Otherwise you can download the source code from the previous tutorial (I will show you how in the next sections) and carry on from there.

Introduction

When the application is in the background we will render a browser notification:

Background chat notifications

And when the application is in the foreground I will show you how to optionally enable in-app notifications using a snackBar:

Foreground chat notificaitons

Setup

To pick up from where we left our application from the previous tutorial, we need to get the repository from the initial commit. We can do that with these commands:

git clone [email protected]:cometchat-pro-tutorials/angular-chat-push-notifications.git
cd angular-chat-push-notifications
git reset --hard 3eae5528ec69e6ddebe03b3dbb399a17ec0bb800

These commands cloned the repository and then we went to a specific commit. Now you can follow along. We can install all the packages and we’re good to go. Inside the project folder run:

npm install

Everything is now set up, and we’re able to work on adding push notifications.

Firebase

CometChat is a proponent of the Unix philosophy which is to do one thing and do it really well. Rather than building push notifications infrastructure CometChat instead focuses on chat and then create extensions which connect you with other services. The CometChat push notifications extension enables you to seamlessly connect with Firebase for chat notifications. Rest assured it is completely free.

To enable push notifications for our app, we need to create a new Firebase project and within that a new Web application.

Head to the Firebase console and click on Add project +:

Firebase dashboard

When creating the project, enter a name, continue and uncheck Enable Google Analytics for this project. Then you’ll be able to hit Create Project and you’re done ✨.

Inside the new project you need to add a web application. Below the title of the new project, you have the Add App button. Click on it and then on the Web App option:

New app in Firebase

Web app option is the third in the list:

Web app selection

Give it a name and hit Register App. On the next step, Add Firebase SDK, copy the firebaseConfig object and paste it in your environment.ts file in the Angular app. It should look something like this:

export const environment = {
  production: false,
  appId: 'cometchat_app_id',
  firebase: {
    apiKey: '<your-key>',
    authDomain: '<your-project-authdomain>',
    databaseURL: '<your-database-URL>',
    projectId: '<your-project-id>',
    storageBucket: '<your-storage-bucket>',
    messagingSenderId: '<your-messaging-sender-id>'
  }
};

Keep Firebase open in a tab as we will need to reference some keys but aside from that, everything is set up in the Firebase dashboard so we can move on and enable notifications in CometChat.

CometChat Extension

To use push notifications, we now need to enable them inside CometChat. Go to the Dashboard and Explore your application:

CometChat dashboard

Inside our app, go to extensions and hit Add Extension on Push Notification. To enable this, we need the server key from Firebase.

Go back to Firebase Console, click on the project that you previously created and go to the settings page of the new application:

Settings link on Firebase web app

From there, go to Cloud Messaging tab (the second one) and fetch the project credentials. Use them inside the CometChat Dashboard to enable the Push Notifications extensions.

This is all the required setup that we need, in both CometChat and Firebase to use push notifications. Now we can add them to our project.

AngularFire

The best way to use Firebase inside your Angular project is through AngularFire. This is the official library and can be found under the Angular project on GitHub.

We first need to install it:

npm install --save @angular/fire

Inside our application module, we now need to import the angular fire module and initialize it. Since we’re using the messaging module to show notifications we need to import that one also:

import { AngularFireModule } from '@angular/fire';
import { AngularFireMessagingModule } from '@angular/fire/messaging';

And we need to add both of them to the imports array of the module, passing in the config object that we’ve previously added to our environment file:

imports: [
  BrowserModule,
  AppRoutingModule,
  AngularFireModule.initializeApp(environment.firebase),
  AngularFireMessagingModule,
],

Angular Fire is now set up and ready to be used 🙂.

Push notifications

When we receive a new message, our application can be in two states. It can be active, and then we get a notification from Firebase that a new message was received and we can do something about it. More importantly, our application can be minimized, or in the background state, and then we need to use a service worker to handle the message.

A service worker is a script that our browser will run in the background, totally separate from our web page. This script will allow us to handle functionality that doesn’t need any user interaction, for example showing notifications.

The problematic part here is that AngularFire does not work with the default service worker provided by Angular, so we have to roll out our own. The good news is that the library will handle everything for us. As long as we provide a script that will be run as a background worker, everything else is handled.

We start by adding a new file called manifest.json inside our src folder:

{
  "short_name": "AngularChatNotifications",
  "name": "CometChat + Angular + Notifications App",
  "start_url": "/",
  "gcm_sender_id": "103953800507"
}

We have a name for our application, a start URL and a sender id. The sender id is a global value, so please don’t change this.

We need to add a reference to this file inside our index.html file:

<link rel="manifest" href="/manifest.json" />

Now we need to add the actual worker script. In the same folder, src, create a new file called firebase-messaging-sw.js. Inside it, we’ll place the functionality to handle notifications when our application is not active.

First, we need to import the Firebase lib:

importScripts('https://www.gstatic.com/firebasejs/7.2.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/7.2.1/firebase-messaging.js');

Then, we need to initialized the lib:

firebase.initializeApp({
  messagingSenderId: 'YOUR-SENDER-ID',
});

We already have the sender id, it’s the same value as messagingSenderId from our Firebase config object inside our environment file. So you can copy and paste it from there.

The last and most important part is to handle any background messages that we get and show a notification:

const messaging = firebase.messaging();
messaging.setBackgroundMessageHandler(function(payload) {
  const sender = JSON.parse(payload.data.message);
  const notificationTitle = 'New CometChat message';
  const notificationOptions = {
    body: payload.data.alert,
    icon: sender.data.entities.sender.entity.avatar,
  };
  return self.registration.showNotification(
    notificationTitle,
    notificationOptions,
  );
});

Using setBackgroundMessageHandler method from Firebase messaging library we register a function that will be called each time a notification is received. First, we parse the message to get the sender and then we create a new notification with the message and the icon of the sender.

Using the showNotification method we create and display it. For more information have a look at the great MDN documentation.

Both of these files need to be added to the assets array inside the angular config file. This is because we want them deployed to our output folder at each build:

"assets": [
  "src/favicon.ico",
  "src/assets",
  "src/firebase-messaging-sw.js",
  "src/manifest.json"
],

Notifications service

Inside our application we need to do two things. Enable notifications and listen for any incoming messages when the app is active. In order to do this, we’ll create a new notifications service:

ng g service core/notifications

To register our app to retrieve notifications, we need to ask the user for permission and get a token from Firebase. Then we need to make a request to the ComeChat API to register this.

Inside the constructor we need to get an instance of the messaging module from AngularFire and of HTTP client from Angular:

export class NotificationsService {
  constructor(
    readonly afMessaging: AngularFireMessaging,
    readonly http: HttpClient,
  ) {}

AngularFire has a streamlined method to request permission and get the token, all in one step. The requestToken observable. Subscribing to this, will request permissions from the user and fetch a token from Firebase.

Once we have this token, we need to make a post request to CometChat API to register that we want to receive notifications. After this is done, notifications are enabled for our application:

enableNotifications() {
  return this.afMessaging.requestToken.pipe(
    map(token => this.getTopicUrl(token)),
    switchMap(url => this.http.post(url, { appId: environment.appId })),
  );
}

Whenever we get a new token value, we map that value to the URL from CometChat where we need to make the request:

private getTopicUrl(token: string) {
  const topic = `${environment.appId}_user_${this.authService.currentUser.uid}`;
  return `https://ext-push-notifications.cometchat.com/fcmtokens/${token}/topics/${topic}`;
}

We create a topic name from our application id, type of the channel for which we want to receive a notification (user or group) and the id of the entity. Then we append it, together with the token to the ComeChat address. If you want to read more about managing notifications in ComeChat have a look at the documentation. It explains almost everything you need, including how to set up a new Firebase project and how to make the POST request to the CometChat API.

After we have the URL, we make a post request to it, with the application id as payload:

this.http.post(url, { appId: environment.appId })

Subscribing to this observable will request permissions from the user, get a token from Firebase and make the request to the API. All in a few lines of code 🙂.

We should also consider what happens when the application has focus. In that case it would be better to render an in-app notification using snackBar. To acomplish this, we can leverage the same AngularFireMessaging and subscribe to the messages observable:

listenForMessages() {
  this.afMessaging.messages.subscribe(
    ({ data }: { data: any }) =>
      this.snackBar.open(`${data.title} - ${data.alert}`, 'Close', {
        duration: 1000,
      }),
    err => console.log(err),
  );
}

Whenever we get a new notification, we extract the title and message from it and use the Snackbar from Angular Material to show it on the UI. To use the Snackbar we have to import it inside the constructor:

constructor(
  readonly afMessaging: AngularFireMessaging,
  readonly authService: AuthService,
  readonly http: HttpClient,
  readonly snackBar: MatSnackBar,
) {}

We also have the AuthService here, since we used it in the method where we retrieved the CometChat URL to retrieve the user id.

Our service is up and done, now we have to use it in our application.

Updating our app

To leverage our notifications, we can go to the chat component and enable them. First, we add the newly created service inside the constructor:

readonly notificationsService: NotificationsService

Then, we can create a method to enable the notifications:

requestPermissions() {
  this.notificationsService.enableNotifications().subscribe(
    _ =>
      this.snackBar.open('Notifications enabled!', 'Close', {
        duration: 1000,
      }),
    err => console.log('Error enabling notifications: ' + err),
  );
}

We call the method from our service and subscribe to the result. If everything worked, we show a Snackbar to let the user know that notifications were enabled.

Now, inside the init method we enable the notifications and listen for new messages:

this.requestPermissions();
this.notificationsService.listenForMessages();

All done! Notifications are not enabled for our application and we’ll see them in both cases. When the application is active, we’ll see a Snackbar and when it’s not we see the system notifications.

Conclusion

Using the CometChat SDK, Firebase and the AngularFire library we were able to add notifications to our application with not that much effort. These tools take care of the hard work for us.

Now we’re able to engage users better in our applications while showing them relevant notifications.