Skip to content
Developers · 8 min read

Angular push notifications with SwPush

Angular has push built in: the @angular/service-worker package’s SwPush service subscribes users and its ngsw-worker.js displays notifications from a specific payload format. Here’s the whole flow and the gotchas.

Updated By the Pusharoo team
In short
  • Add the service worker with ng add @angular/pwa; SwPush only works in production builds where the worker is enabled.
  • Call swPush.requestSubscription({ serverPublicKey }) from a click handler and post the result to your server.
  • ngsw-worker.js shows a notification only when the payload has a “notification” object.
  • Use data.onActionClick to control what a click opens.

1. Add the service worker

ng add @angular/pwa // app.config.ts (standalone apps) import { provideServiceWorker } from '@angular/service-worker'; import { isDevMode } from '@angular/core'; export const appConfig = { providers: [ provideServiceWorker('ngsw-worker.js', { enabled: !isDevMode(), registrationStrategy: 'registerWhenStable:30000' }) ]};

Because the worker is disabled in dev mode, test push with ng build and a static server over HTTPS or on localhost.

2. Subscribe with SwPush

import { Component, inject } from '@angular/core'; import { SwPush } from '@angular/service-worker'; import { HttpClient } from '@angular/common/http'; @Component({ selector: 'app-notify', template: `<button (click)="enable()" [disabled]="!swPush.isEnabled">Get notifications</button>` }) export class NotifyComponent { swPush = inject(SwPush); http = inject(HttpClient); async enable() { const sub = await this.swPush.requestSubscription({ serverPublicKey: 'YOUR_VAPID_PUBLIC_KEY' }); this.http.post('/api/push/subscribe', sub).subscribe(); } }

3. The payload ngsw expects

{ "notification": { "title": "Build #482 passed", "body": "main → production in 3m 12s", "icon": "/icons/icon-192x192.png", "actions": [{ "action": "logs", "title": "View logs" }], "data": { "onActionClick": { "default": { "operation": "navigateLastFocusedOrOpen", "url": "/builds/482" }, "logs": { "operation": "openWindow", "url": "/builds/482/logs" } } } } }

Send that JSON from any Web Push library (Node, Python, Go). Without a top-level notification key, ngsw won’t display anything — it only forwards the message to open pages via swPush.messages.

4. Listen in the app

this.swPush.messages.subscribe(msg => console.log('push while open', msg)); this.swPush.notificationClicks.subscribe(({ action, notification }) => { /* analytics */ });

Common pitfalls

  • swPush.isEnabled is false in dev mode or on HTTP — that’s expected.
  • Ask on a click; Chrome and Firefox quiet or block prompts without a gesture.
  • Angular’s data groups don’t matter for push, but a stale ngsw.json after deploy can delay worker updates — check /ngsw/state.
  • iPhone requires a Home Screen install on iOS 16.4+; the manifest from ng add @angular/pwa helps.

Show the right state and allow opt-out

// subscription$ emits the current PushSubscription or null this.swPush.subscription.subscribe(sub => this.enabled = !!sub); async disable() { const sub = await firstValueFrom(this.swPush.subscription); if (sub) await firstValueFrom(this.http.post('/api/push/unsubscribe', { endpoint: sub.endpoint })); await this.swPush.unsubscribe(); }

Hide the button entirely when swPush.isEnabled is false or Notification.permission is denied, and explain how to re-enable in browser settings instead.

Sending the ngsw payload from Node

const payload = JSON.stringify({ notification: { title: 'Build #482 passed', body: 'main → production in 3m 12s', data: { onActionClick: { default: { operation: 'navigateLastFocusedOrOpen', url: '/builds/482' } } } }}); await webpush.sendNotification(subscription, payload, { TTL: 3600 });

Any library works — the key point is the top-level notification object.

Ionic and Capacitor apps

Autocomplete suggests many people search “Angular Capacitor push notifications”. Inside a Capacitor native shell, notifications use native APIs (APNs, FCM) through a Capacitor plugin, not the web Push API. The SwPush approach applies to the web/PWA build of the same app. Plan both if you ship to the App Store and the web.

Using a hosted service with Angular

One scope can only have one service worker. If you want a hosted service instead of your own sender, you can keep Angular’s caching worker and add the service’s push handling to it, or drop ngsw if you don’t need offline caching. With Pusharoo, put the snippet in src/index.html and either upload sw.js (if you don’t use ngsw) or create a small custom worker that loads both:

// src/custom-sw.js — list it in angular.json "assets" and register it instead of ngsw-worker.js importScripts('./ngsw-worker.js'); self.PUSHAROO_SITE = 'pk_your_site_key'; importScripts('https://pusharoo.com/sw.js');

Point the snippet at it with data-sw="/custom-sw.js" and test both caching and push in a production build.

Questions

Why is SwPush not working in development?

The Angular service worker is only enabled in production builds by default; swPush.isEnabled is false otherwise.

Do I need Firebase for Angular push notifications?

No. SwPush uses the standard Push API with your VAPID public key. Firebase is optional.

Why doesn’t my notification show?

ngsw needs a payload with a top-level “notification” object. Also check OS notification settings and Focus modes.

Can I use Firebase Cloud Messaging with Angular instead of SwPush?

Yes. AngularFire or the Firebase JS SDK can get an FCM token and display messages through Firebase’s own service worker. That adds a Google project and replaces ngsw for push. For web-only apps, SwPush with standard VAPID keys is simpler and works with any Web Push sending library.

How do I test Angular push notifications locally?

Build for production (ng build), serve the dist folder on localhost with any static server, subscribe in Chrome, then send a test with a Web Push library or use DevTools → Application → Service workers → Push with a JSON payload containing a notification object.

More in Developers: VAPID keys for web push, explained · The Web Push protocol, explained · Web push notifications with Node.js · Web push notifications in PHP · Laravel browser push notifications (no Firebase) · Declarative Web Push, explained · Firebase web push vs. standard web push · Web push in Python with pywebpush · Django push notifications to the browser · Web push notifications in Ruby on Rails · Web push in Go with webpush-go · Push notifications in SvelteKit · All guides

Free for life. No limits.

Unlimited subscribers, campaigns and automations. No badges. About five minutes to install.