Verdict
The plan is sound. The sequencing (Firebase swap first on Angular 20, then framework, then libraries) is the right call, the checklist covers every breaking change in the 21.0.0 release notes, and every version number resolves on the registry. Two claims are wrong in a way that changes what you do in Phase 2, and there are a handful of inventory gaps that would have surfaced as surprises in Phase 4 or 6.
Verified correct
These claims were checked and hold. They are listed so the reader knows what does not need re-verifying.
- Current state. Core 20.3.31, CLI 20.3.37, TypeScript 5.8.3, Node 24.5.0, npm 12.0.1, zone.js 0.15.1, Material and CDK 20.2.14, firebase 11.10.0 pulled in transitively by
@angular/fire20.0.1. Commits64a1a3d259and35f314c117exist with the described messages. - Every target version exists and is the newest in its 21 line. Core 21.2.23, CLI 21.2.24, Material 21.2.14, TypeScript 5.9.3,
@angular-eslint21.4.0, primeng 21.1.10, ngx-bootstrap 21.2.2, ng-select 21.8.2,@ngrx/store21.1.1, ng-bootstrap 20.0.0, ngx-toastr 20.0.5, ngx-mask 21.1.0, ngx-owl-carousel-o 21.0.3, ngx-infinite-scroll 21.0.0,@zxing/ngx-scanner21.0.0, angular-fontawesome 4.0.0, ngx-device-detector 11.0.0. - Peer ranges match. primeng wants
@angular/core ^21.0.7, ngx-bootstrap^21.2.0, zxingtypescript ^5.9,@angular-eslintallowseslint ^8.57 || ^9 || ^10. Angular 21 itself peerszone.js ~0.15 || ~0.16andtypescript >=5.9 <6.1. - Angular 22 requirements. Node
^22.22.3 || ^24.15.0 || >=26and TypeScript>=6.0 <6.1, exactly as the plan says. - Builders survive.
@angular-devkit/build-angular@21.2.24still shipskarmaandserver, so thetestandserverlesstargets keep working. - SSR advisory. GHSA-x288-3778-4hhx is patched in
@angular/ssr21.1.5 (and 20.3.17).allowedHostsis already passed toCommonEngineatserver.ts:191. - AngularFire null-on-server behaviour. Both
provideMessagingandprovideRemoteConfigreturnnullwhenisPlatformServer. The&& this.remoteConfigcheck ingetRemoteConfigValueis what keeps theisPrerenderedpath safe today. The plan's analysis of that is right. @angular/fireis stuck. Thenexttag is21.0.0-rc.0,latestis 20.0.1, and the rc depends onfirebase ^12.4.0. Nothing newer exists.- Codebase counts. 0 spec files, 46 files touching
NgZone, 0 hits formoduleId,interpolation,ngComponentOutletContent,ApplicationConfig,ignoreChangesOutsideZone,lastSuccessfulNavigation,NgModuleFactory,emitDeclarationOnly.@angular/elementsappears only in a comment inmain.server.ts. OnlyformArrayNameis used in templates.FirebaseServiceis referenced in 15 files. The service worker loads compat 9.6.1 from gstatic. - SSR shape.
AppServerModuleimportsAppModule, so the zone provider migration covers the server bundle too.CommonEnginestill accepts an NgModule type asbootstrap.
Wrong or misleading
Ordered by how much they change the work.
-
.npmrcalready setslegacy-peer-deps=trueThe repo has a committed
.npmrccontaininglegacy-peer-deps=true. That single line invalidates three statements in the plan:- "All three already fail
npm lspeer checks today." They do not.npm lsexits 0 with zeroinvalidentries because peer resolution is switched off. - "AngularFire still needs
--legacy-peer-deps" as a reason to drop it. Every install in this repo already runs that way, so it is not a differentiator. The Firebase 12 coupling is the real reason, and it stands on its own. - Phase 2: "add an
overridesblock rather than--forceso futurenpm installruns are clean without a flag." The flag is already permanent, andoverridesdoes not touch the check that will actually fail.
ng updateruns its own peer check against installedpeerDependencies(@angular/cli/src/commands/update/schematic/index.js:198) and throws Incompatible peer dependencies found. The only way past it is--force.ng-ivs(declares Angular 12),ngx-audio-player(17) andngx-stripe(18) will all trigger it. Plan for--forceup front instead of discovering it mid-phase. - "All three already fail
-
Untracked files also block
ng updateThe cleanliness check is
Fix: commit them, add them togit status --porcelain(update/cli.js:724), and??entries count as dirty. Phase 0 says to commit or stash the 8 modified files, but the 12 untracked*.htmlplans in the repo root (this plan included) will stopng updatejust the same..gitignore, or pass--allow-dirty. Do this in Phase 0, not when the command refuses. -
The
@HostBindinginventory is incompleteThe plan names three components plus
video-player-v2. Two more files use@HostBindingand will be type-checked for the first time in 21:src/app/components/skeleton/profile-skeleton/profile-skeleton.component.ts:class.main-contentgetter, typedboolean, probably fine.src/app/modules/auth/components/signup-by-refer/signup-by-refer.component.ts:style.--profile-urlon an untyped getter.
The
header.component.tsgetters are also untyped. Thehost: {}scan is correct: 37 files declarehostmetadata but onlyvideo-player-v2has actual[...]bindings, the rest are static attributes. -
CDK usage is undercounted
The plan says "~25" files for drag-drop and clipboard. The real number is 33 for those two entry points, and 35 overall once you add
@angular/cdk/scrollinginpromotional.module.tsand@angular/cdk/layoutinshared/services/mobile-optimization.service.ts. Neither of the extra two appears in the Phase 6 smoke list. -
@angular-devkit/architectversion is misstatedThe matrix lists it moving to 21.2.24. The architect package uses its own scheme; the matching release is
0.2102.24.ng updatehandles it, so this is a documentation error, not a build risk. -
ngx-stripe and stripe-js
The plan says ngx-stripe 21 "forces
@stripe/stripe-jsfrom 3 to 5 or later." The latest 21 release (21.9.0) requires>=9 <10. Separately, the installed ngx-stripe 18.1.0 already declares@stripe/stripe-js >=4 <5against installed 3.5.0, so stripe-js is out of range today, not only after an upgrade. It also sits indevDependencies, which is odd for a runtime SDK. All of this belongs in the deferred-task note. -
Small counts
Material bottom-sheet: 6 files import
MatBottomSheet, 7 import from the entry point (one module file). Untracked files: 12, not 11, once the plan itself is written to the repo root.
Gaps
Things the plan does not mention that the repo makes relevant.
-
Custom
UrlSerializerhas a dedicated router changeThe 21.0.0 router notes include "Respect custom
The serializer only overridesUrlSerializerhandling of query parameters (#64449)". This repo providesTrailingSlashUrlSerializerasUrlSerializeratapp.module.ts:98. That is a targeted behaviour change for exactly this class, separate from the general microtask-timing item the plan already lists.parseand delegatesserialize, so it will most likely keep working, but it deserves a verify row in the checklist and a smoke case such as/username/?key=xand/creators?key=x. -
requestFirebaseTokenhas no browser guardIts only caller is
The plan's contract list says "guard withheader.component.ts:825. Today it survives on the server because_messagingisnulland the promise.catchswallows the failure. With the raw SDK,getMessaging()throws synchronously on the server before any.catchcan run.isBrowser", but only calls outgetRemoteConfigValue. Apply the same guard, plusisSupported(), to the token path. -
Zone migration is mandatory, not a prompt
Phase 2 says "say yes to the zone change detection migration when prompted." The
provideZoneChangeDetectionmigration inng update @angular/core@21is a required migration and runs without asking. Harmless, but the checkpoint should read "confirm the provider landed inAppModule.providers" rather than expecting a prompt. -
The extra CDK entry points need smoke coverage
cdk/layoutdrivesmobile-optimization.service.ts(breakpoint observation) andcdk/scrollingis imported by the promotional module. Add "mobile breakpoint switch" and "promotional list scroll" to Phase 6.
Corrected Phase 2
What the framework step should say once the findings above are folded in.
git add -A && git commit -m "chore: commit marketing data and plan files before ng update"
ng update @angular/core@21 @angular/cli@21 --force
ng update @angular/material@21 --force
--force is needed because ng update checks installed peer ranges itself and .npmrc cannot influence that. It does not skip migrations. The provideZoneChangeDetection() migration runs automatically; verify it appears in AppModule.providers before committing. Do not add an overrides block for the three out-of-range libraries; legacy-peer-deps already covers npm install, and overrides would not affect the ng update check.
How this was checked
| Claim type | Evidence |
|---|---|
| Installed versions and peer ranges | node_modules/*/package.json read directly for every package named in the plan. |
| Target versions | npm view <pkg>@21 version peerDependencies for each library; dist-tags for @angular/fire. |
| Builders in 21.2.24 | builders.json from the published @angular-devkit/build-angular@21.2.24 tarball. |
| Advisory | gh api /advisories/GHSA-x288-3778-4hhx. |
| Breaking changes list | Angular 21.0.0 GitHub release notes, Breaking Changes section, cross-checked row by row against the plan's table. |
| Codebase occurrences | rg over src/ and server.ts for each symbol, decorator, and import path. |
ng update behaviour |
Read @angular/cli/src/commands/update/cli.js and update/schematic/index.js for the dirty-tree check and the peer compatibility check. |
| Install state | .npmrc, npm ls exit code, git status --porcelain. |