Appearance
Notifications Push dans une PWA Angular
Dans ce tutoriel, nous allons découvrir comment implémenter les notifications push dans votre Progressive Web App (PWA) Angular. Vous apprendrez à envoyer des notifications à vos utilisateurs même quand l'application est fermée, à gérer les abonnements, et à offrir une expérience utilisateur comparable à une application mobile native.
Qu'est-ce qu'une notification push ?
Imaginez que vous utilisez une application mobile comme Instagram. Même si vous avez fermé l'application, vous recevez quand même des notifications pour vous informer qu'un ami a aimé votre photo ou qu'un nouveau message vous attend. Les notifications push permettent exactement la même chose pour une application web : votre serveur peut envoyer une alerte à votre navigateur — même si l'application est fermée, ou que l'onglet n'est pas actif.
Avec une application web classique, vous êtes "passif" : l'application ne peut vous "rappeler" quelque chose que si vous êtes dessus ou si vous visitez le site. Avec les notifications push, c'est l'inverse : l'application (via un serveur et un service worker) peut envoyer une alerte à votre navigateur, même si l'application est fermée.
Pourquoi utiliser les notifications push ?
Les notifications push apportent plusieurs avantages à votre application :
Re-engagement de l'utilisateur
Les notifications permettent d'informer un utilisateur d'un événement important — nouveau contenu, message, action — même s'il n'utilise pas l'application. Cela l'incite à revenir et augmente l'engagement.
Expérience proche d'une application native
Les notifications web modernes peuvent apparaître comme celles d'une application mobile ou desktop native. Cela rapproche l'expérience d'une PWA d'une vraie application native.
Flexibilité et personnalisation
Vous pouvez décider quand et à qui envoyer des notifications (via votre backend et les abonnements). Cela permet de gérer des notifications ciblées, des alertes personnalisées, des rappels, etc.
Retenir l'utilisateur
Dans un contexte PWA, les notifications augmentent la valeur de l'application, au-delà d'un simple site web. L'utilisateur se sent plus connecté à votre application.
Comment Angular gère les push notifications
Angular fournit un support natif des notifications push dans le cadre de son service worker. Voici ce qu'offre Angular :
La classe SwPush
Angular fournit une classe SwPush, injectable, pour gérer facilement l'abonnement aux notifications. Cette classe simplifie l'utilisation des APIs Web complexes (Push API, Notifications API, Service Workers API).
La gestion des abonnements
Une méthode permet de demander la permission à l'utilisateur. Si l'utilisateur accepte, le navigateur renvoie un PushSubscription — un objet qui identifie de façon unique l'abonné. Cet objet devra être envoyé à votre serveur backend pour qu'il puisse envoyer des notifications à ce navigateur spécifique.
Le service worker en arrière-plan
Le service worker d'Angular gère les notifications même si l'application est fermée. Il reçoit les messages push du serveur et affiche la notification à l'utilisateur, sans que l'application soit nécessairement ouverte.
La gestion des clics sur notification
Si l'utilisateur clique sur la notification (ou sur un bouton d'action), Angular peut réagir — par exemple ouvrir une fenêtre, rediriger vers une URL, ou exécuter une action spécifique.
Concrètement, Angular masque (simplifie) la complexité des APIs Web en fournissant un service facile à manipuler dans votre application.
Étapes pour mettre en place les push avec Angular
Voici un workflow simple pour activer les notifications push dans une application Angular / PWA.
Prérequis : Avoir le service worker Angular activé
Pour que les notifications push fonctionnent, vous devez avoir le service worker Angular activé. Si ce n'est pas encore fait, vous pouvez ajouter le support PWA à votre projet :
bash
ng add @angular/pwaCette commande configure automatiquement le service worker, le manifest, et toutes les fonctionnalités PWA de base. Pour plus de détails sur la configuration d'une PWA avec Angular, consultez le tutoriel sur les PWA.
Service Worker requis
Les notifications push nécessitent absolument un service worker actif. Sans service worker, les notifications push ne fonctionneront pas, même si vous demandez la permission à l'utilisateur.
Étape 1 : Générer les clés VAPID
VAPID (Voluntary Application Server Identification) est un standard qui permet d'identifier votre serveur pour envoyer des notifications push. Vous devez générer une paire de clés (privée et publique).
Pour générer ces clés, vous pouvez utiliser un package Node.js comme web-push :
bash
npm install -g web-push
web-push generate-vapid-keysCela affichera quelque chose comme :
Public Key: BEl62iUYgUivxIkv69yViEuiBIa40HI...longue_clé...
Private Key: 8vdOCs4iS0FquzGeijvdXUnJyei72S...longue_clé...La clé publique sera utilisée côté client (Angular) pour demander l'abonnement. La clé privée sera utilisée côté serveur pour signer et envoyer les notifications.
Stocker les clés en sécurité
La clé privée doit absolument rester secrète et ne jamais être exposée dans votre code client. Elle doit être stockée dans des variables d'environnement côté serveur.
Étape 2 : Injecter SwPush dans votre composant ou service
Dans votre code Angular, vous devez injecter le service SwPush. Voici comment procéder :
typescript
import { Component, inject, signal, computed } from '@angular/core';
import { SwPush } from '@angular/service-worker';
import { HttpClient } from '@angular/common/http';
import { toSignal } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-user-notifications',
standalone: true,
imports: [],
template: `
<div>
<h2>Notifications</h2>
@if (!isSubscribed()) {
<button (click)="subscribeToNotifications()">
Activer les notifications
</button>
} @else {
<p>✓ Vous êtes abonné aux notifications</p>
<button (click)="unsubscribe()">
Désactiver les notifications
</button>
}
@if (error()) {
<p class="error">{{ error() }}</p>
}
</div>
`
})
export class UserNotificationsComponent {
// Service Angular pour gérer les notifications push
// Il permet de demander l'abonnement, gérer les clics, etc.
private swPush = inject(SwPush);
// Service HTTP pour envoyer l'abonnement au serveur backend
private http = inject(HttpClient);
// Clé publique VAPID - à remplacer par votre propre clé
// Cette clé permet d'identifier votre serveur lors de l'abonnement
private readonly VAPID_PUBLIC_KEY = 'BEl62iUYgUivxIkv69yViEuiBIa40HI...';
// Signal qui contient la subscription actuelle (null si pas d'abonnement)
// On convertit l'observable subscription en signal pour une meilleure intégration
private subscription = toSignal(this.swPush.subscription, { initialValue: null });
// Signal dérivé qui indique si l'utilisateur est abonné
// Il est automatiquement mis à jour quand subscription change
isSubscribed = computed(() => !!this.subscription());
// Message d'erreur si quelque chose se passe mal
// Utilisation d'un signal pour une gestion réactive de l'état d'erreur
error = signal<string | null>(null);
constructor() {
// Vérifie si les notifications push sont supportées au démarrage
// Si non supportées, on définit directement le message d'erreur dans le signal
if (!this.swPush.isEnabled) {
this.error.set('Les notifications push ne sont pas supportées sur ce navigateur.');
}
}
// Demande l'abonnement aux notifications push à l'utilisateur
// Si l'utilisateur accepte, on obtient un PushSubscription qu'on envoie au serveur
subscribeToNotifications() {
this.error.set(null);
// Demande l'abonnement en fournissant la clé publique VAPID
// Le navigateur va demander la permission à l'utilisateur
this.swPush.requestSubscription({
serverPublicKey: this.VAPID_PUBLIC_KEY
})
.then(sub => {
// L'utilisateur a accepté, on a reçu un PushSubscription
// Il faut maintenant l'envoyer à notre serveur backend
this.sendSubscriptionToServer(sub);
})
.catch(err => {
// L'utilisateur a refusé ou une erreur s'est produite
console.error('Erreur lors de l\'abonnement:', err);
this.error.set('Impossible de s\'abonner aux notifications. ' +
(err.message || 'Vérifiez que vous avez accepté les permissions.'));
});
}
// Envoie l'abonnement au serveur backend
// Le serveur utilisera cet abonnement pour envoyer des notifications plus tard
private sendSubscriptionToServer(subscription: PushSubscription) {
// Envoie l'abonnement à votre API backend
// Le serveur stockera cet abonnement pour pouvoir envoyer des notifications
this.http.post('/api/notifications/subscribe', {
subscription: subscription.toJSON()
}).subscribe({
next: () => {
console.log('Abonnement envoyé au serveur avec succès');
},
error: (err) => {
console.error('Erreur lors de l\'envoi de l\'abonnement au serveur:', err);
this.error.set('Impossible d\'enregistrer l\'abonnement sur le serveur.');
}
});
}
// Désabonne l'utilisateur des notifications push
unsubscribe() {
this.swPush.unsubscribe()
.then(() => {
// Optionnel : informer le serveur que l'utilisateur s'est désabonné
this.http.post('/api/notifications/unsubscribe', {}).subscribe();
})
.catch(err => {
console.error('Erreur lors du désabonnement:', err);
this.error.set('Impossible de se désabonner.');
});
}
}Analysons ce code étape par étape :
Injection de
SwPush: Le serviceSwPushest injecté pour gérer les notifications push. C'est l'interface principale fournie par Angular pour interagir avec les notifications.Conversion de l'observable en signal :
toSignal()convertit l'observableswPush.subscriptionen signal, ce qui permet une meilleure intégration avec le système de signaux d'Angular. Le signal sera automatiquement mis à jour quand l'abonnement change.Signal dérivé avec
computed():isSubscribedest un signal dérivé qui calcule automatiquement si l'utilisateur est abonné en se basant sur la valeur desubscription. Il se met à jour automatiquement quandsubscriptionchange.Vérification du support : Le constructeur vérifie directement si les notifications push sont supportées. Si ce n'est pas le cas, un message d'erreur est défini dans le signal
error.Demande d'abonnement :
swPush.requestSubscription()demande la permission à l'utilisateur et retourne unePushSubscriptionsi l'utilisateur accepte. Cette méthode prend en paramètre la clé publique VAPID.Envoi au serveur : Une fois l'abonnement obtenu, il doit être envoyé à votre serveur backend. Le serveur utilisera cet abonnement pour envoyer des notifications plus tard.
Gestion réactive de l'état : Les signaux permettent une gestion réactive de l'état sans avoir besoin de souscriptions manuelles ou de cycle de vie. Les valeurs se mettent à jour automatiquement dans le template.
Étape 3 : Écouter les clics sur les notifications
Quand un utilisateur clique sur une notification, vous pouvez réagir — par exemple, rediriger vers une page spécifique ou exécuter une action. Voici comment procéder :
typescript
import { Component, inject } from '@angular/core';
import { SwPush } from '@angular/service-worker';
import { Router } from '@angular/router';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-root',
standalone: true,
template: `<router-outlet />`
})
export class AppComponent {
// Service pour gérer les notifications push
private swPush = inject(SwPush);
// Router pour naviguer vers une page spécifique lors d'un clic sur notification
private router = inject(Router);
constructor() {
// Écoute les clics sur les notifications
// On s'abonne directement à l'observable dans le constructeur
// takeUntilDestroyed() gère automatiquement le nettoyage à la destruction du composant
// Cela évite d'avoir besoin d'OnDestroy et de gérer manuellement les souscriptions
this.swPush.notificationClicks
.pipe(takeUntilDestroyed())
.subscribe(notificationData => {
console.log('Notification cliquée:', notificationData);
// Récupère l'action sur laquelle l'utilisateur a cliqué
const action = notificationData.action;
// Récupère les données de la notification
const data = notificationData.notification.data;
// Si les données contiennent une URL, on navigue vers cette URL
if (data && data.url) {
this.router.navigateByUrl(data.url);
}
// Si l'utilisateur a cliqué sur une action spécifique, on peut la gérer
if (action === 'view') {
// Action "view" : ouvrir une page
this.router.navigateByUrl(data?.url || '/');
} else if (action === 'close') {
// Action "close" : fermer la notification
console.log('Notification fermée');
}
});
}
}Ce code :
S'abonne directement dans le constructeur : L'abonnement à l'observable
notificationClicksse fait directement dans le constructeur. Cela permet de capturer chaque événement de clic sans avoir besoin de cycle de vie complexe.Gestion automatique du nettoyage :
takeUntilDestroyed()gère automatiquement le désabonnement à la destruction du composant. Cela évite les fuites mémoire sans avoir besoin d'implémenterOnDestroyou de gérer manuellement les souscriptions.Récupère les données : Les données de la notification (envoyées par le serveur) sont accessibles via
notificationData.notification.data.Gère les actions : Si vous avez défini des boutons d'action dans votre notification (comme "Voir" ou "Fermer"), vous pouvez les gérer via
notificationData.action.Navigue vers une URL : Un cas d'usage courant est de rediriger l'utilisateur vers une page spécifique de l'application, par exemple vers le détail d'un utilisateur si la notification concerne un nouvel utilisateur.
Avantages de takeUntilDestroyed()
L'utilisation de takeUntilDestroyed() élimine le besoin d'implémenter OnDestroy et de gérer manuellement les souscriptions. Le nettoyage est géré automatiquement par Angular lors de la destruction du composant. C'est une approche moderne et recommandée pour gérer les observables dans les composants Angular.
Étape 4 : Envoyer une notification depuis le serveur
Les notifications push nécessitent un serveur backend pour "pousser" le message — ce n'est pas l'application Angular seule qui décide d'envoyer. Angular gère seulement la réception via le service worker et l'affichage.
Voici un exemple de serveur Node.js utilisant le package web-push :
typescript
// server.ts (exemple de serveur backend)
import express from 'express';
import webpush from 'web-push';
const app = express();
app.use(express.json());
// Configuration des clés VAPID
const VAPID_PUBLIC_KEY = 'BEl62iUYgUivxIkv69yViEuiBIa40HI...';
const VAPID_PRIVATE_KEY = '8vdOCs4iS0FquzGeijvdXUnJyei72S...';
webpush.setVapidDetails(
'mailto:[email protected]',
VAPID_PUBLIC_KEY,
VAPID_PRIVATE_KEY
);
// Stockage des abonnements (en production, utilisez une base de données)
const subscriptions: PushSubscription[] = [];
// Endpoint pour recevoir les abonnements depuis le client Angular
app.post('/api/notifications/subscribe', (req, res) => {
const subscription = req.body.subscription;
subscriptions.push(subscription);
res.json({ success: true });
});
// Endpoint pour envoyer une notification à tous les abonnés
app.post('/api/notifications/send', (req, res) => {
const { title, body, url } = req.body;
const payload = JSON.stringify({
title: title || 'Notification',
body: body || 'Vous avez un nouveau message',
icon: '/assets/icon-192x192.png',
badge: '/assets/badge.png',
data: {
url: url || '/'
},
actions: [
{
action: 'view',
title: 'Voir'
},
{
action: 'close',
title: 'Fermer'
}
]
});
// Envoie la notification à tous les abonnés
const promises = subscriptions.map(sub =>
webpush.sendNotification(sub, payload)
.catch(err => {
console.error('Erreur lors de l\'envoi:', err);
// Si l'abonnement n'est plus valide, on peut le retirer de la liste
if (err.statusCode === 410) {
const index = subscriptions.indexOf(sub);
if (index > -1) {
subscriptions.splice(index, 1);
}
}
})
);
Promise.all(promises)
.then(() => {
res.json({ success: true, sent: promises.length });
})
.catch(err => {
res.status(500).json({ error: err.message });
});
});
app.listen(3000, () => {
console.log('Serveur démarré sur http://localhost:3000');
});Ce serveur :
Configure les clés VAPID : Utilise les mêmes clés VAPID que celles utilisées côté client (clé publique) pour l'abonnement.
Stocke les abonnements : Reçoit les abonnements depuis le client Angular et les stocke (dans cet exemple, en mémoire — en production, utilisez une base de données).
Envoie des notifications : Permet d'envoyer des notifications à tous les abonnés en utilisant
webpush.sendNotification().Gère les erreurs : Si un abonnement n'est plus valide (code 410), il est retiré de la liste.
Package web-push
Le package web-push est une bibliothèque populaire pour envoyer des notifications push depuis Node.js. Installez-le avec npm install web-push.
Contraintes et bonnes pratiques
L'utilisateur doit accepter les notifications
La permission est demandée via le navigateur. Sans permission → pas de notification. L'utilisateur peut refuser, et vous devez gérer ce cas gracieusement.
HTTPS obligatoire
Il faut servir l'application via HTTPS (ou localhost pour développement) pour que le service worker soit actif. Les notifications push ne fonctionneront pas sur HTTP en production.
HTTPS requis
Le service worker et les notifications push nécessitent HTTPS (sauf localhost). Assurez-vous que votre application est servie via HTTPS en production.
Support dépendant du navigateur
Le support dépend du navigateur / de l'environnement. Si le navigateur ne supporte pas les service workers ou la Push API, les notifications ne fonctionneront pas. Toujours vérifier le support avant d'essayer d'utiliser les notifications.
Un serveur backend est nécessaire
Les notifications push nécessitent un serveur backend pour "pousser" le message — ce n'est pas l'application Angular seule qui décide d'envoyer. Angular gère seulement la réception via le service worker et l'affichage.
Gérer les abonnements expirés
Les abonnements peuvent expirer ou devenir invalides. Votre serveur doit gérer ces cas (code d'erreur 410) et retirer les abonnements invalides de votre base de données.
Bonnes pratiques pour les notifications
- Ne pas spammer : N'envoyez pas trop de notifications, au risque que l'utilisateur se désabonne.
- Contenu pertinent : Assurez-vous que le contenu des notifications est pertinent pour l'utilisateur.
- Actions claires : Si vous ajoutez des boutons d'action, assurez-vous qu'ils sont clairs et utiles.
- Titre et corps concis : Les notifications doivent être courtes et claires.
Bonne pratique
Informez toujours l'utilisateur de l'utilité des notifications avant de demander la permission. Par exemple : "Activez les notifications pour être informé des nouveaux messages et des mises à jour importantes."
