FCM (Firebase Cloud Messaging) aka Push Notifications tutorial | Firebase | Angular | Angularfire 17 [2024]

Paweł Idziak
7 min readMar 28, 2024

In this article, we will quickly go through the configuration of FCM (Firebase Cloud Messaging) aka Push Notifications for Angular framework using Angularfire (@angular/fire v17).

#1 — Overview

Long story short, FCM also known as Push Notifications, is a solution that allows sending messages between devices and/or servers to keep everything up to date. When someone sends you a message or there’s new data to sync, FCM ensures your app gets notified. 📩📱 So, whether you’re chatting with friends or getting updates from your favorite app, FCM quietly works behind the scenes to ensure you’re always in the loop! You can consider the FCM as the silent hero that keeps your app connected and your users informed! 🚀🌟

What’s important FCM works on the VAPID key and device TOKEN. In simple words, thanks to the VAPID key we authorize the server we want to listen to, and by token, FCM understands to which device we should send a message.

THE TOKEN IS GENERATED PER DEVICE, NOT THE USER!

If you still feel like something isn’t clear, I recommend watching the official Firebase messaging video.

Also, for more details, check out this in-depth official guide.

#2 — Firebase configuration & Vapid key

Assuming we already have a Firebase project, our configuration in Angular should look similar to this:

import { NgModule } from '@angular/core';

import { provideFirestore, getFirestore } from '@angular/fire/firestore';
import { getMessaging, provideMessaging } from '@angular/fire/messaging';
import { provideFirebaseApp, initializeApp } from '@angular/fire/app';
import { getAuth, provideAuth } from '@angular/fire/auth';

import { environment } from '../environments/environment';

@NgModule({
imports: [
provideFirebaseApp(() => initializeApp(environment.firebase)),
provideFirestore(() => getFirestore()),
provideAuth(() => getAuth()),
provideMessaging(() => getMessaging()), // 👈👈👈 importing messaging module
],
})
export class FirebaseModule {}

Where in our environment file should have the Firebase config look like this:

export const environment = {
...
firebase: {
apiKey: '...',
authDomain: '...',
projectId: '...',
storageBucket: '...',
messagingSenderId: '...',
appId: '...',
measurementId: '...',
},
vapidKey: '...', (check the secion below)
};

NOTE! FirebaseModuleshould be imported into our App/Shell module. Also remember to add @angular/fire dependency, in our case it’s 7.6.1 version.

Vapid Key

According to the docs, the FCM Web interface uses Web credentials called VAPID (Voluntary Application Server Identification) keys to authorize requests to supported web push services. So, to subscribe the app to push notifications, we need to provide this key to the Firebase config (we can put it in the environment file like shown above, to easily get it for example by DI later).

We can take or generate the VAPID key from:

  1. Go to Firebase project settings, then move to the Cloud Messaging tab
  2. Scroll down to Web configuration and generate the new key.

#3 — Getting the device token

Great, now we can get a token that will be assigned to our device 📱.

NOTE! “FCM requires a firebase-messaging-sw.js file. Unless you already have a firebase-messaging-sw.js file, create an empty file with that name and place it in the root of your domain before retrieving a token. You can add meaningful content to the file later in the client setup process.” — from the docs.

If you already have the firebase-messaging-sw.js (even empty) add it to your project assets configuration (in angular 17 it’s in project.json file) and you can go to implementation inside the Angular component (otherwise firebase won’t register the service worker and you will see messaging/failed-service-worker-registration in the console).

import { Component, inject } from '@angular/core';

import { Messaging, getToken, onMessage } from '@angular/fire/messaging';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
private readonly _messaging = inject(Messaging);
private readonly _env = inject(ENVIRONMENT);

ngOnInit(): void {
this._getDeviceToken();
this._onMessage();
}

private _getDeviceToken(): void {
getToken(this._messaging, { vapidKey: this._env.vapidKey })
.then((token) => {
console.log(token);
// save the token in the server, or do whathever you want
})
.catch((error) => console.log('Token error', error));
}

private _onMessage(): void {
onMessage(this._messaging, {
next: (payload) => console.log('Message', payload),
error: (error) => console.log('Message error', error),
complete: () => console.log('Done listening to messages'),
});
}
}

Ok, what’s going on here? 🤨

First, we’ve got our env file by using Dependency Injection (for PoC you can import it directly).

Then, the _getDeviceToken method does a couple of things: (1) subscribes the Messaging instance to push notifications, (2) gets the unique token and automatically saves in the device storage (in Chrome, you can check it in IndexedDB Storage, under the firebase-messaging-database/firebase-messaging-store), and additionally (3) if notification permission isn’t already granted, this method asks the user for permission. The returned promise is rejected if the user does not allow the app to show notifications.

In the end, in _onMessage function, we subscribe to the messages from the server. What’s important here, next callback will be called when a message is received while the app has focus (the app is in the foreground).

#4 — Service Worker

“In order to receive the onMessage event, your app must define the Firebase messaging service worker in firebase-messaging-sw.js. Alternatively, you can provide an existing service worker to the SDK through getToken()“— but we won’t over the second scenario right now. To cover the first scenario, in firebase-messaging-sw.js file just add those lines of code:

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

const app = firebase.initializeApp({
apiKey: '...',
authDomain: '...',
projectId: '...',
storageBucket: '...',
messagingSenderId: '...',
appId: '...',
measurementId: '...',
});

firebase.messaging(app);

Nice, huh? If you are wondering about entering your API key, your concerns are valid but if you have well-structured secure rules — don't worry about it. For more info please see the Frank van Puffelen answer HERE — wonderfully explained.

Anyway, if you are hosting your app in Firebase, you can also do an interesting trick here. In this case, it’s enough to import some files relatively, like this:

importScripts('/__/firebase/9.8.0/firebase-app-compat.js');
importScripts('/__/firebase/9.8.0/firebase-messaging-compat.js');
importScripts('/__/firebase/init.js');

So we can have two files: for development and production, and swap them appropriately.

#5 — Sending a message

For PoC, I’m just gonna cover the simplest solution — sending a message from a Firebase console (UI). <spoiler: sending a message from a Cloud Function will be in a separate article :)>

So, to send it from a Firebase project UI go to your project, then (1) go to Messaging (it’s under Engage), (2) click Create your first campaign button, (3) select Firebase Notification messages, (4) enter notification data and click Send test message button. (5) Now just add the device token you printed in the console in the previous step and (6) click the Test button.

Uff, if everything is good, and you are not focused on the app <and god is on your side 😆>, you should see the notification in the browser corner! If you click on it, you will be redirected to the app.

If you are focused on the app (you can open the Firebase console UI and the app side by side in two tabs), you should see the console log with the message:

Aaand… that’s pretty much it. If you have any questions, please let me know! 🤙 Have fun and enjoy! Stay tuned for the continuation. 👍

Catch some TIPS, if you have a hard time with FCM:

  • you need to enable notification permission to run the whole FCM machine,
  • importing script versions in the service worker should be aligned with your Firebase package version,
  • if you get errors related to the service worker, try to unregister it first (dev tools — application — service workers), or register it manually (by providing it in getTokenmethod),
  • remember to always check your device token when sending a message (for example from the Firebase console)— it may differ and you won’t see the message,
  • you can also manually delete the generated device token from the chrome devtools: application — firebase-messaging-database/firebase-messaging-store,
  • remember to add firebase-messaging-sw.js in your project assets configuration (not assets folder!),
  • Learn the best practices for handling the tokens (like saving them and updating on refreshing) from the docs,
  • Last but not least, if you want to customize incoming messages, you can do it in firebase-messaging-sw.js like this:
...

const messaging = firebase.messaging(app);

messaging.onBackgroundMessage((payload) => {
// Customize notification here, do sth with payload
const notificationTitle = 'Modified Message Title';
const notificationOptions = {
body: 'Modified body.',
icon: '/app-logo.png'
};
self.registration.showNotification(notificationTitle, notificationOptions);
});

--

--