Learn PWA in Angular — Manifest, Service Worker and iOS Meta Tags

Oct 16, 2026AngularEngineering
Summarize with AI: Google AI Claude ChatGPT Perplexity Grok

AI links open with a title + excerpt (these tools can't fetch the page themselves) — use "Copy full article" to paste the complete text for a fuller summary.

Share:

Introduction

This article demonstrates how to turn an Angular app into an installable Progressive Web App, so a visitor can add it to their home screen and open it like a native app.

There are three parts. The manifest tells the browser what the app is called and what icon to use. The service worker makes it work offline. And then iOS needs its own set of tags, because Safari does not read the manifest the way everyone else does.

The Web Manifest

frontend/public/manifest.webmanifest.

{
  "name": "My Site",
  "short_name": "My Site",
  "display": "standalone",
  "orientation": "portrait-primary",
  "scope": "/",
  "start_url": "/",
  "background_color": "#f4f6fc",
  "theme_color": "#193f7e",
  "icons": [ /* … */ ]
}

The fields that actually change behaviour.

display: "standalone" is what makes it feel like an app. No address bar, no browser tabs. The alternatives are browser (a normal tab, so why install), minimal-ui (a small toolbar) and fullscreen (nothing at all, for games).

start_url is where a tap on the icon opens. Ours is /. If your app has a dashboard, point it there — an installed app should not open on a marketing page.

scope: "/" is the boundary of the app. A link outside the scope opens in a normal browser instead of inside your installed window.

background_color is shown during launch, before your app renders. Match it to your real page background — a white splash before a dark app is a flash your user sees every single launch.

short_name is what appears under the icon. Keep it short, or the phone truncates it.

Link it in index.html.

<link rel="manifest" href="manifest.webmanifest">

Maskable Icons

We ship each icon size twice — once as any, once as maskable. Eight sizes each, from 72px to 512px.

{ "src": "icons/icon-192x192.png",          "sizes": "192x192", "type": "image/png", "purpose": "any" },
{ "src": "icons/maskable-icon-192x192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }

The reason is Android. Different launchers crop icons into different shapes — circle, squircle, rounded square, teardrop. A maskable icon is designed for that: the important content sits inside a safe zone in the middle, with the background extending to all four edges.

If you only supply an any icon, Android does one of two bad things — it crops your logo, or it drops your square icon into a white circle with a visible border. Both look wrong next to real apps.

My suggestion: make one maskable source image with about 20% padding on every side, then export all the sizes from it. Check the result in Chrome DevTools → Application → Manifest, which previews the masked shapes.

The manifest and icons in Chrome DevTools Application tab

The Service Worker

Angular ships one. Enable it in app.config.ts.

provideServiceWorker('ngsw-worker.js', {
  enabled: !isDevMode(),
  registrationStrategy: 'registerWhenStable:30000',
}),

Its behaviour comes from ngsw-config.json, which lists what to cache and how.

registrationStrategy: 'registerWhenStable:30000' waits until the app is stable before registering, falling back to 30 seconds if it never settles. That keeps the worker from competing for bandwidth with your first paint.

Two things to understand before you enable this, because a service worker is the one piece of this that can cause real problems.

enabled: !isDevMode() keeps it off during development. A service worker caching your app while you are editing it is a genuinely confusing experience — you change a file, reload, and see the old version. Leave it off locally.

A service worker is sticky. Once installed on a visitor's device it keeps serving cached files until it updates. Ship a broken one and you cannot simply deploy a fix, because the broken worker is what is serving the page. Angular's worker handles updates properly, which is the main argument for using it rather than writing your own.

The iOS Meta Tags

Safari supports the manifest for some things and ignores it for others, so iOS needs its own tags in index.html.

<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="My Site">
<link rel="apple-touch-icon" href="icons/icon-192x192.png">

apple-mobile-web-app-capable is the one that gives you standalone mode on iOS — without it, "Add to Home Screen" produces a bookmark that opens in Safari with the address bar. apple-touch-icon is needed because Safari does not take the icon from the manifest. apple-mobile-web-app-title is the home screen label.

Be honest about iOS with your stakeholders. There is no install prompt — the user must find Share → Add to Home Screen themselves. Push notifications require iOS 16.4 or later and only work once installed. Storage can be evicted after a period of disuse. An installed PWA on iOS is a genuinely good experience; getting the user to install it is the hard part, and no tag fixes that.

The app installed on a home screen and running standalone

Serve the Manifest With the Right Type

One deployment detail that will waste your afternoon. Azure Static Web Apps does not know the .webmanifest extension by default, so it serves the file with the wrong content type and the browser ignores it. No error, no install prompt, nothing.

In staticwebapp.config.json:

"mimeTypes": {
  ".webmanifest": "application/manifest+json"
}

Also check your navigationFallback exclude list includes webmanifest, or a request for it returns your index.html with a 200 status.

Both are the same class of bug, and both are invisible: the request succeeds, the content is wrong, and nothing reports it.

Check Your Work

Chrome DevTools → Application → Manifest shows the parsed manifest, the icons with their masked previews, and a list of anything missing for installability. The Service Workers panel shows whether yours registered.

Then run Lighthouse and read the PWA section. It names the exact missing field rather than making you guess.

Conclusion

In this article we learned how to make an Angular app installable — a manifest with display: standalone and a matching background_color, maskable icons for Android launchers, Angular's service worker disabled in development, the extra tags Safari needs, and the MIME type Azure needs to serve the manifest at all.

The parts that break are rarely the manifest itself. They are the icon shapes, the content type, and the assumption that iOS behaves like Android.

Reference

#pwa#angular#service-worker#manifest#ios#azure-static-web-apps

Comments

Be the first to comment.

Leave a comment

Never shown publicly.

Comments are reviewed before appearing publicly.