What Changed in Browser APIs and the Angular Service Worker from 2020 to 2026: Reading Baseline
Baseline tells whether a feature is in every core browser; Angular 22 targets the browsers that were widely available on a fixed date (Chrome 119, Firefox 119, Safari 17), so newly available APIs such as the Navigation API still need feature detection. A report that reads the status of the series' features from web-features, the milestones from 2020 to 2026, the API changes in the Angular Service Worker and an audit checklist for an old Angular PWA.
Version note: The examples in this post were run with Angular 22.2 (TypeScript 6.0), @angular/build 22.2.7, @angular/service-worker 22.2.6, web-features 3.46.0, Node.js 24 and Playwright 1.65. Support data was read from MDN browser-compat-data 8.1.9 and web-features 3.46 (November 2026); the APIs of older Angular versions were compared from the type definitions of the packages on npm.
Predict First
The Angular client of Bookstore, the online bookstore used as the shared example across this site's series, is built with Angular 22, and the Angular CLI's default browser target is "Baseline widely available". A teammate reasons: "Angular already targets only Baseline browsers; the Navigation API became Baseline in January 2026. So we can use navigation.addEventListener without feature detection." Are they right?
In this post we look back. For someone who wrote an Angular PWA in 2020, what changed on the browser and the Angular side, how do you read Baseline labels, and what should you check when bringing an old Angular PWA up to date? We gather every feature used in the previous nine posts into a single report.
What Is Baseline and How Do You Read It?
Baseline is a label that the WebDX Community Group at the W3C started in 2023: it says whether a web platform feature is supported as stable in every browser of the "core browser set", that is the desktop and mobile versions of Chrome, Edge, Firefox and Safari. It has two levels. Newly available is the day the feature reaches the last browser in that set; widely available is 30 months after that day. The 30 months reflect the assumption that most users will have updated to that browser version by then. A feature that is neither has "limited availability": at least one browser lacks it.
The label comes from the web-features package. This package defines features in the units a developer thinks in (for example "cookie-store") and links each one to the detailed keys in MDN's browser-compat-data (BCD) (for example api.CookieStore.get). That link matters, because a feature as a whole and the part you actually use can be in different states. In this series' storage post we wrote that cookieStore reached every engine with Firefox 140 and Safari 18.4; web-features, however, does not count the "cookie-store" feature as Baseline. The reason is in the report below: the feature also includes CookieStoreManager, which lets a service worker subscribe to cookie changes, and Safari does not have it. cookieStore.get and set, on the other hand, have been Baseline Newly Available since June 2025. When reading Baseline, look at the keys you use, not at the feature's name.
The Features Bookstore Uses: a Baseline Report
Instead of tabulating the status of the browser features we used across the series by hand, I wrote a small script that reads it from web-features. The package is a devDependency of the root project (npm run baseline):
// 10: the Baseline status of the browser features this series uses, read from web-features
// Run: npm run baseline
import { features } from "web-features";
// [web-features id, post, the compat keys we really call (when the feature is larger than what we use)]
const used: [string, string, string[]?][] = [
["abortsignal-any", "01"],
["fetch-request-streams", "01"],
["fetchlater", "01"],
["cookie-store", "02", ["api.Window.cookieStore", "api.CookieStore.get", "api.CookieStore.set"]],
["origin-private-file-system", "02"],
["storage-buckets", "02"],
["indexeddb", "03"],
["service-workers", "04"],
["manifest", "06"],
["beforeinstallprompt", "06"],
["navigator-install", "06"],
["badging", "06"],
["push", "07"],
["background-sync", "07"],
["scheduler", "08"],
["shared-workers", "08"],
["event-timing", "08"],
["web-locks", "09"],
["broadcast-channel", "09"],
["navigation", "09"],
];
type Status = { baseline: "high" | "low" | false; baseline_low_date?: string; baseline_high_date?: string; support: Record<string, string> };
function describe(status: Status): string {
if (status.baseline === "high") return `widely available since ${status.baseline_high_date}`;
if (status.baseline === "low") return `newly available since ${status.baseline_low_date}`;
const engines = Object.keys(status.support).filter((b) => !b.endsWith("_android") && !b.endsWith("_ios"));
return `limited: ${engines.join(", ") || "no stable browser"}`;
}
for (const [id, post, keys] of used) {
const feature = features[id] as { kind: string; status: Status & { by_compat_key?: Record<string, Status> } };
console.log(`${post} ${id.padEnd(27)} ${describe(feature.status)}`);
for (const key of keys ?? []) console.log(` ${key.padEnd(30)} ${describe(feature.status.by_compat_key![key])}`);
}
01 abortsignal-any widely available since 2026-09-19
01 fetch-request-streams limited: chrome, edge
01 fetchlater limited: chrome, edge
02 cookie-store limited: chrome, edge, firefox
api.Window.cookieStore newly available since 2025-06-24
api.CookieStore.get newly available since 2025-06-24
api.CookieStore.set newly available since 2025-06-24
02 origin-private-file-system widely available since 2025-09-27
02 storage-buckets limited: chrome, edge
03 indexeddb widely available since 2024-03-20
04 service-workers widely available since 2020-10-30
06 manifest limited: chrome, edge, safari
06 beforeinstallprompt limited: chrome, edge
06 navigator-install limited: no stable browser
06 badging limited: chrome, edge, safari
07 push widely available since 2025-09-27
07 background-sync limited: chrome, edge
08 scheduler limited: chrome, edge, firefox
08 shared-workers newly available since 2026-05-05
08 event-timing newly available since 2025-12-12
09 web-locks widely available since 2024-09-14
09 broadcast-channel widely available since 2024-09-14
09 navigation newly available since 2026-01-13
The report reads like a summary of the series. Service workers, IndexedDB, Push, OPFS, Web Locks and BroadcastChannel are widely available: they can be used today without feature detection. The Navigation API, Event Timing and SharedWorker are newly available: present in recent versions, but possibly missing on an older iPhone. Upload streaming, fetchLater, Storage Buckets, Background Sync and beforeinstallprompt exist only in Chromium. navigator.install does not yet appear in any stable browser in web-features; according to BCD it is behind a flag in Chrome 154, and in the installable PWA post we saw it defined without a flag in Playwright's Chromium 158. Manifest and Badging are "limited": Firefox desktop has no manifest-based installation, and Badging is missing in Firefox and Chrome Android. This report depends on the web-features version: the package is updated every month, and running the same script with a new version changes the statuses.
Angular's Browser Target: "baseline widely available on"
In the source of the Angular CLI's build package, the default browserslist query is written with a fixed date: baseline widely available on 2026-05-07 in @angular/build 22.2.7. If the project has no .browserslistrc, the build uses this query. I resolved the query with browserslist 4.29:
and_chr 157 ... 157 (1 versions)
and_ff 160 ... 160 (1 versions)
chrome 119 ... 157 (39 versions)
edge 119 ... 156 (38 versions)
firefox 119 ... 160 (42 versions)
ios_saf 17.0 ... 27.0 (21 versions)
safari 17.0 ... 27.2 (23 versions)
Here is the answer to the prediction: no. Angular targets the browsers that support the features widely available as of 7 May 2026; the lowest versions are Chrome and Edge 119, Firefox 119, Safari and iOS 17.0. The Navigation API arrived in Safari 26.2 and only became newly available in January 2026. An iPhone that Angular targets (iOS 17 or 18) has no navigation. In addition, this target only determines the JavaScript syntax and CSS that Angular compiles; it adds no polyfills for browser APIs. You have to check the status of every API you use in your own code: if it is not widely available, feature detection is required.
Milestones on the Browser Side from 2020 to 2026
By 2020 service workers were already widely available (October 2020, according to web-features); an app that worked offline with the Angular Service Worker was possible in every major browser. What was missing was the app behaving like an "app" on iOS. The biggest change came in March 2023 with iOS 16.4: Web Push, the Badging API and the manifest's id for web apps added to the home screen. In the same period OPFS and OffscreenCanvas became Baseline with Safari 16.4, and Compression Streams in May 2023 with Firefox 113. Push thus became newly available in March 2023 and widely available in September 2025.
2024 brought AbortSignal.any (March 2024, combining the user's cancel with a timeout in the Fetch post), and IndexedDB became widely available. In 2025 the core API of cookieStore reached every engine with Firefox 140 (June 2025), and Event Timing became Baseline with Safari 26.2 (December 2025). In 2026 the Navigation API became Baseline in January with Firefox 147 and Safari 26.2, SharedWorker in May with Chrome Android 148; with Chrome 155 the removal of the unload event was completed (in the tabs post we saw it never run in Chromium 158). In the same period Chromium moved ahead on its own: fetchLater and native Observable arrived in Chrome 135, navigator.install behind a flag in Chrome 154. Background Sync, Periodic Background Sync, Storage Buckets and beforeinstallprompt are still Chromium-only.
What Changed in the Angular Service Worker
Instead of writing the Angular changes from memory, I compared the type definitions of the @angular/service-worker packages on npm version by version (from 12.2 to 22.0; for the router I looked at the 22.2 types):
| Version | Change |
|---|---|
| 13.0 | SwUpdate.versionUpdates arrived; available and activated deprecated; checkForUpdate() and activateUpdate() now return Promise<boolean> |
| 16.0 | provideServiceWorker() for standalone apps |
| 17.0 | SwUpdate.available and SwUpdate.activated removed |
| 20.1 | SwPush.notificationCloses and SwPush.pushSubscriptionChanges |
| 21.1 | Experimental withExperimentalPlatformNavigation() in the router (Navigation API) |
| 22.0 | HttpClient uses Fetch by default, withFetch() deprecated |
I tried compiling a typical update check written in 2020-2021 with Angular 22:
// an Angular 12 era update check, compiled against Angular 22
import { inject } from '@angular/core';
import { SwUpdate } from '@angular/service-worker';
export function legacyUpdates() {
const updates = inject(SwUpdate);
updates.available.subscribe(() => updates.activateUpdate().then(() => document.location.reload()));
updates.activated.subscribe((event) => console.log(event.previous, event.current));
}
✘ [ERROR] TS2339: Property 'available' does not exist on type 'SwUpdate'. [plugin angular-compiler]
src/app/legacy-check.ts:7:10:
✘ [ERROR] TS2339: Property 'activated' does not exist on type 'SwUpdate'. [plugin angular-compiler]
src/app/legacy-check.ts:8:10:
✘ [ERROR] TS7006: Parameter 'event' implicitly has an 'any' type. [plugin angular-compiler]
src/app/legacy-check.ts:8:31:
Its counterpart is AppUpdates from the Angular Service Worker post: wait for the VERSION_READY event in versionUpdates, show the user a banner and leave the reload to them. The habit in the old code of calling activateUpdate() and reload() as soon as a new version arrives is also a problem: it can wipe out a form the user is filling in. ServiceWorkerModule.register() still exists in 22 and works; in a standalone app provideServiceWorker() does the same job.
An Audit Checklist for an Old Angular PWA
I gathered the checks from every post in this series into one list. When you inherit an Angular PWA written between 2020 and 2022, go through these in order:
- Update check: are
versionUpdatesandunrecoverableused instead ofavailable/activated, and does the user decide when to reload? (05) - Deploy headers: are
ngsw.json,index.html,ngsw-worker.jsand the manifestno-cache, are hashed filesimmutable, and are old hashed files kept for a few versions? (05) dataGroups: do per-user endpoints (orders, cart, profile) stay out of every group? NGSW ignoresVary. (05)- Kill switch: is the procedure for deploying
safety-worker.jswritten down? (04, 05) - Manifest: is there an
id, areanyandmaskableicons separate, are therescreenshotsand adescription? (06) - Install offer and permissions: does the app avoid asking for installation or notification permission as soon as the page opens? (06, 07)
- Web Push: is the VAPID private key in a secret store, are subscriptions that return 404/410 deleted, are endpoints checked against an allow-list? (07)
- Storage: is
QuotaExceededErrorcaught, ispersist()requested for data that must stay, is large JSON kept in localStorage? (02, 03) - Tabs: is shared work (outbox, polling, token refresh) given to one tab with Web Locks, and is
unloadused on close? (09) - Main thread: are heavy calculations in a worker, and is INP measured in the field? (08)
- Feature detection: is there a check and a fallback for every API that is not widely available (
navigation,scheduler.yield,setAppBadge,fetchLater)? (10)
In Production
Feature detection and progressive enhancement are two sides of the same thing. Detection checks whether an API exists ('setAppBadge' in navigator, 'scheduler' in globalThis); enhancement makes sure the app still does its core job when the API is missing. All the examples in this series follow this pattern: without a badge the number shows in the header, without scheduler.yield there is setTimeout, without Background Sync there is the online event, without beforeinstallprompt there are instructions for Safari. Always check the feature itself instead of guessing the browser from the user agent string; we saw in the Web Worker post that TypeScript's lib.dom.d.ts may assume an API exists everywhere.
Keep the Baseline data current. web-features and BCD are updated often; the report in this post was taken with version 3.46 and will give different results in a few months. Running the report in CI once a month and reviewing the changes lets you make "we no longer need feature detection" decisions with data. Your users' real browser distribution matters too: for a Bookstore whose institutional customers work with an old Windows and an old Edge, even widely available may not always be enough; look at the browser versions in your analytics.
Common Pitfalls
- Saying "Angular targets Baseline, so I can use every Baseline API". Angular's target is widely available and tied to a fixed date; newly available APIs need feature detection.
- Reading a feature's Baseline status from its name. Look at the BCD keys you use; as in the
cookieStoreexample, the part and the whole can differ. - Leaving the service worker code to the compiler when upgrading Angular.
availableis a compile error, but the "reload as soon as it arrives" behaviour compiles and stays a problem. - Using Chromium-only APIs (
beforeinstallprompt, Background Sync,fetchLater) as the only path. Safari and Firefox users need a fallback. - Relying on an old test device or an emulator without looking at your real users' browser distribution.
A Small Experiment
★☆☆ Add two more features the series uses to baseline-report.ts: view-transitions and compression-streams. Write down their status and since when they have had it.
★★☆ Add a .browserslistrc to Bookstore, change the target from baseline widely available to baseline 2024, and read the warning ng build prints. What does it say when you go outside the range Angular supports?
★★★ Turn baseline-report.ts into an audit tool that searches the source code for API names (navigator.locks, navigation., scheduler.yield, setAppBadge…) and prints the status of each and whether the file has feature detection for it. If you find a use in Bookstore that is not widely available and has no feature detection, fix it.
Bookstore's Angular client reads data as a stream with Fetch, picks the right storage, keeps an offline outbox in IndexedDB, opens offline with the Angular Service Worker, can be installed, receives notifications, does heavy work in a worker and coordinates its tabs. The series' last post covers, hands-on, the APIs through which the app talks to the device: clipboard, sharing, keeping the screen awake, fullscreen, permissions and location. Measuring and improving the performance impact of these capabilities is the topic of the Web Performance series.