From 7c641a6d184625c2929b8376ef1a4b5b948f758b Mon Sep 17 00:00:00 2001 From: Armando Navarro Date: Sat, 31 Oct 2026 03:07:27 -0700 Subject: [PATCH 1/3] docs: say which wrapped calls do not hold the app unstable docs/zones.md says callback APIs hold the app unstable until an initial value, so a server render waits for data. #3827 makes a set of calls exceptions because their first callback may never come. Waiting on a callback that never comes would keep the app from ever becoming stable. List those calls in docs/zones.md, and point at onValue, object, list, listVal and fromRef with the value event as the calls that still make a server render wait. In docs/database.md, say the same under stateChanges, auditTrail and fromRef. Refs #3826 --- docs/database.md | 6 ++++++ docs/zones.md | 2 ++ 2 files changed, 8 insertions(+) diff --git a/docs/database.md b/docs/database.md index 1eac2244b..dcc1f39ea 100644 --- a/docs/database.md +++ b/docs/database.md @@ -100,6 +100,8 @@ The `listVal()` function creates an observable that emits an object mapped to it The `stateChanges()` function creates an observable that emits each time a change occurs at the reference or query passed. This is useful for tracking the changes in your list. The optional `events` parameter will filter which child events populate the array. +It emits only on child events, which an empty list never produces, so it does not hold the application unstable and a server render does not wait for it. Use `list()` when the render should wait for a list's initial contents. + | | | |-----------------|------------------------------------------------------| | **function** | `stateChanges(ref, options?)` | @@ -110,6 +112,8 @@ The `stateChanges()` function creates an observable that emits each time a chang The `auditTrail()` function creates an observable that emits the entire state trail. This is useful for debugging or replaying the state of a list in your app. The optional `events` parameter will filter which child events populate the array. +Like `stateChanges()`, it waits for child events, so it does not hold the application unstable and a server render does not wait for it. + | | | |-----------------|------------------------------------------------------| | **function** | `auditTrail(ref, options?)` | @@ -120,6 +124,8 @@ The `auditTrail()` function creates an observable that emits the entire state tr The `fromRef()` function creates an observable that emits reference changes. +With `ListenEvent.value` it holds the application unstable until the first value is emitted, so a server render waits for the data. With a child event it does not, because it may never emit. + | | | |-----------------|------------------------------------------| | **function** | `fromRef(ref, event)` | diff --git a/docs/zones.md b/docs/zones.md index 99aa0de58..ca1d8df4f 100644 --- a/docs/zones.md +++ b/docs/zones.md @@ -10,6 +10,8 @@ These wrappers ensure Firebase APIs are called outside of the Angular zone. This Observables, Promise-based APIs, and those with callbacks will purposely destabilize your application until a initial value is returned, this ensures that server-side rendering (SSR) and static site generation (SSG/pre-rendering) wait for data before rendering the page's HTML. +Some calls are exceptions. They are still zone wrapped, but they do not hold the application unstable, because their first callback may never come or a render has no reason to wait for them: `onLog`, `onTokenChanged`, `beforeAuthStateChanged`, `onMessage`, `onRegistered`, `onUnregistered`, `logEvent`, `isSupported`, and in the Realtime Database `onChildAdded`, `onChildChanged`, `onChildMoved`, `onChildRemoved`, `off`, `enableLogging`, `stateChanges`, `auditTrail`, and `fromRef` with a child event. To make a server render wait for Realtime Database data, use `onValue`, `object`, `list`, `listVal`, or `fromRef` with `ListenEvent.value`. + ## Consequences of not Zone wrapping When using a Firebase or RxFire API without importing from AngularFire or if AngularFire APIs are used outside of an injection context you _may_ experience instability. From d7cbf2c3457f9c44e2104f09b4ac715d40207278 Mon Sep 17 00:00:00 2001 From: Armando Navarro Date: Wed, 4 Nov 2026 09:17:18 -0800 Subject: [PATCH 2/3] docs: describe firebase 13 support in the v22 upgrade guide #3831 lets AngularFire 22 accept firebase 12 or 13, and the ng update migration now writes the range of the firebase major already installed. The upgrade guide still said the migration writes ^12.18.0 and quoted peer warnings that name only that range. State the supported range and what the migration writes, update the Yarn 1 and pnpm 9/10 warnings to their current wording, and add a section on moving to firebase 13: VertexAIBackend is removed and its packages declare Node 24.12 or later. Refs #3830 --- docs/version-22-upgrade.md | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/docs/version-22-upgrade.md b/docs/version-22-upgrade.md index 693a4a7bc..8a9b6a512 100644 --- a/docs/version-22-upgrade.md +++ b/docs/version-22-upgrade.md @@ -1,6 +1,6 @@ # Upgrading to the AngularFire 22 release candidate -AngularFire 22 targets **Angular 22** and the **Firebase JS SDK v12**. Most of the upgrade is handled for you by `ng update`. +AngularFire 22 targets **Angular 22** and the **Firebase JS SDK v12 or v13**. Most of the upgrade is handled for you by `ng update`. ## Run the update @@ -14,8 +14,10 @@ Include `@angular/fire@next` in the same command as Angular. On its own, `ng upd The AngularFire update runs a migration that: -- **Adds** `firebase` **to your** `package.json` **if it is missing**, as `"firebase": "^12.18.0"`, and reinstalls. See the next section for why. -- **Aligns an existing** `firebase` **entry to** `^12.18.0` when it requests a version outside that range. +- **Adds** `firebase` **to your** `package.json` **if it is missing**, and reinstalls. See the next section for why. +- **Aligns an existing** `firebase` **entry** when it requests a version outside `^12.18.0 || ^13.0.0`, the range AngularFire 22 supports. + +Either way, the migration writes `^13.0.0` when firebase 13 is already installed and `^12.18.0` otherwise, so an app on firebase 12 stays on 12 and an app on firebase 13 stays on 13. ## Your app now owns the Firebase SDK @@ -23,7 +25,7 @@ Through AngularFire 21, `@angular/fire` listed `firebase` under `dependencies`, AngularFire 22 lists `firebase` under `peerDependencies` instead. So your app should declare `firebase` in its `package.json` (on Yarn it must, since Yarn installs no peer dependencies). This gives you exactly one copy of `firebase`, and an incompatible version is refused at install time instead of failing at runtime. -`ng update @angular/fire@next` updates an older `firebase` entry in your `package.json` to `^12.18.0` when it requests a version outside that range. If your app doesn't list `firebase` in `package.json` the migration adds `"firebase": "^12.18.0"` and reinstalls. Verify with `npm ls firebase`, which should show exactly one copy. +`ng update @angular/fire@next` updates an older `firebase` entry in your `package.json` when it requests a version outside `^12.18.0 || ^13.0.0`. If your app doesn't list `firebase` in `package.json`, the migration adds it and reinstalls. Verify with `npm ls firebase`, which should show exactly one copy. ### If you install with `npm install` instead of `ng update` @@ -49,14 +51,31 @@ Do not use `--force` or `--legacy-peer-deps` to get past this message. Both inst Yarn doesn't install peer dependencies. Add `firebase` to your own `package.json` as shown above, or run `ng update @angular/fire@next`, which adds it. -An older version installs anyway with a warning (`has incorrect peer dependency "firebase@^12.18.0"` on Yarn 1, `YN0060: INCOMPATIBLE_PEER_DEPENDENCY` on Yarn 2+), so treat that warning as an upgrade step you still have to do. +An older version installs anyway with a warning (`has incorrect peer dependency "firebase@^12.18.0 || ^13.0.0"` on Yarn 1, `YN0060: INCOMPATIBLE_PEER_DEPENDENCY` on Yarn 2+), so treat that warning as an upgrade step you still have to do. ### pnpm -pnpm 8 and later install a missing `firebase` automatically. An older version installs anyway with a warning (`unmet peer firebase@^12.18.0: found 11.x` on pnpm 9 and 10, `Issues with peer dependencies found. Run "pnpm peers check" to list them.` on pnpm 11 and 12), and pnpm prints that warning only once, when it first resolves the tree, never on a later install from the committed lockfile. +pnpm 8 and later install a missing `firebase` automatically. An older version installs anyway with a warning (`unmet peer firebase@"^12.18.0 || ^13.0.0": found 11.x` on pnpm 9 and 10, `Issues with peer dependencies found. Run "pnpm peers check" to list them.` on pnpm 11 and 12), and pnpm prints that warning only once, when it first resolves the tree, never on a later install from the committed lockfile. Set `strict-peer-dependencies=true` in `.npmrc` to make pnpm refuse the mismatch outright. +## Firebase 13 is optional + +AngularFire 22 works with firebase 12 (`^12.18.0`) and firebase 13 (`^13.0.0`). `ng update` keeps the firebase major your app already has, so moving to firebase 13 is a separate step you can take when you are ready: + +```bash +npm install firebase@^13.0.0 +``` + +Before you update, check these firebase 13 changes: + +- **`VertexAIBackend` is removed** from `firebase/ai`, and so from `@angular/fire/ai`. Use `AgentPlatformBackend`, which firebase 12.18 also exports, so you can switch before you upgrade. + - `VertexAIBackend` defaults to the `us-central1` location and `AgentPlatformBackend` to `global`, so replace `new VertexAIBackend()` with `new AgentPlatformBackend('us-central1')` to keep the same location. + - A call that already passes a location keeps it as is. +- **Node.js 24.12 or later.** firebase 13's packages declare `"node": ">=24.12.0"`, so on Node.js 22 npm prints `EBADENGINE` warnings when it installs them. + +For everything else that changed, see the [Firebase JavaScript SDK release notes](https://firebase.google.com/support/release-notes/js). + ## Fewer build-time packages `@angular/fire` no longer installs `@angular-devkit/schematics`, `@angular-devkit/core` or `@schematics/angular`. The Angular CLI supplies them when it runs `ng add` and `ng update`, so nothing changes for an Angular CLI or Nx workspace. From 40b07fa20a535aea88d6ebf5f7f1189ed2331732 Mon Sep 17 00:00:00 2001 From: Armando Navarro Date: Wed, 4 Nov 2026 09:39:23 -0800 Subject: [PATCH 3/3] docs: describe microtask delivery in the v22 upgrade guide #3833 moves every Observable read from a setInterval timer to the next microtask checkpoint. As a breaking change it needs a section in the upgrade guide: which tests now see a value they used to wait for, the setTimeout ordering flip, and the zoneless server render case for hand-written TransferState entries. Refs #3832 --- docs/version-22-upgrade.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/version-22-upgrade.md b/docs/version-22-upgrade.md index 8a9b6a512..e93200fee 100644 --- a/docs/version-22-upgrade.md +++ b/docs/version-22-upgrade.md @@ -88,6 +88,19 @@ Deploying server-side rendering to Cloud Functions with `ng deploy` (a deploy ta Hosting-only deploys (`ssr` absent or `false`) and Cloud Run deploys (`ssr: 'cloud-run'`) are not deprecated. They no longer print the old "Legacy ng-deploy Firebase is deprecated" message. +## Observable reads deliver as microtasks + +Every Observable read (`collectionData`, `docData`, `authState`, `user`, `objectVal`, `listVal` and the rest) used to hand each value to your subscriber on a timer (`setInterval` with no delay), one event-loop turn later. It now does so at the next microtask checkpoint, which is sooner. The compat `AngularFireAuth` streams change the same way. What arrives, in which order, and when change detection runs do not change. + +What an upgrading app may notice: + +- A test that waits only for microtasks (a few awaited promises, vitest fake timers without advancing, `fakeAsync` with `flushMicrotasks()` alone) now sees a value whose source emits in a microtask, as Auth and a promise-based mock do. + - In a test that fakes timers, a Firestore value still arrives only after the clock is advanced with `tick()` or `vi.advanceTimersByTime()`, because the Firestore SDK hands each snapshot over inside a `setTimeout`. + - A test that already advances the clock, or waits for a real timer, sees the value exactly as before. +- Code that registers its own `setTimeout` when a read emits sees that timer run after the value arrives, not before. +- In a zoneless server render, a value arriving between the app reporting stable and serialization now reaches a `TransferState` entry you write by hand but not the HTML, because the zoneless change-detection pass is itself a later timer. + - Before this change such a value reached neither. AngularFire itself writes nothing into `TransferState`. + ## Other notes - **Node.js 20 is not supported.** Angular 22 requires Node.js `^22.22.3 || ^24.15.0 || >=26.0.0`. Node.js 20 reached end of life on 2026-04-30.