How to Run an Angular Major Version Upgrade Across Every Repo
Mandy Singh
July 9, 2026 · // 9 min read
Angular ships a major release roughly every six months. Most organizations run one or two majors behind, because the upgrade never reaches the top of any single team’s sprint. The result is a fleet of frontends sitting at different versions, each carrying its own drift.
The command that runs the upgrade is not the hard part. ng update handles a single repository at a time, and only when the working tree is clean and the previous major’s deprecations are already resolved. One frontend sits at Angular 15, another at 16 with a pinned RxJS, a third still imports HttpClientModule and ships a bespoke angular.json builder config. Reconciling every one of those drift points so the schematic applies cleanly is where the quarter goes.
Across an organization’s frontends, the upgrade becomes a coordination project. The code change is small in any one repo. Doing it everywhere, consistently, and verifying nothing regressed is the actual work.
An Angular major version upgrade across multiple repos means reconciling each app’s version drift, applying the equivalent of ng update in every repository, replacing deprecated APIs, and aligning peer dependencies and CI runtimes to the target major. The bottleneck across an organization is coordinating that same sequence over dozens of frontends, not the upgrade in any one repo.
Why does an Angular upgrade stall across an organization?
An Angular upgrade stalls because ng update operates one clean repository at a time, and across an org no two repositories are in the same state. Each version introduces deprecations, schematic migrations, and peer-dependency requirements that compound across the dependency graph. A major bump often forces a TypeScript bump, a zone.js bump, and a higher Node engine floor. Angular Material and CDK have to move in lockstep with @angular/core or peer resolution fails outright.
The migrations only apply cleanly once the previous major’s deprecations are gone. A repo two majors behind cannot jump straight to the target. It has to walk the intermediate migration first. Multiply that across a fleet of apps, each with its own pinned RxJS, its own legacy defaults, and its own builder config, and the work is no longer running a command. It is getting every repository into a known, consistent starting state so the command can run at all.
Angular ships a major release roughly every six months, and its CLI migrates one major at a time, which is why organizations with many frontends drift into a spread of versions that each need reconciling before any upgrade applies.
The method: reconcile the drift, then apply the schematic
The reliable way to run this everywhere is to treat drift reconciliation as the first step, not a surprise discovered mid-upgrade. Get each repo to a consistent baseline, align the peer dependencies, then let the schematic do its job. The version bump is the easy part. The peer-dependency alignment and the deprecated-API rewrites are what decide whether the production build passes.
The repeatable sequence, applied to every repository:
- Inventory the current state. Read
package.json,angular.json, andtsconfig.jsonto record the current Angular major and the surface area each app uses. - Bump the framework packages. Move every
@angular/*package to the target major and align peer dependencies: RxJS, TypeScript, zone.js, and the Node engine floor. - Move Material and CDK in lockstep. Bump Angular Material and CDK to the same major as
@angular/core, or peer-dependency resolution fails. - Apply the migrations in-tree. Run the equivalent of the version’s
ng updateschematics:@ViewChildstatic defaults,HttpClientModulereplaced withprovideHttpClient, RxJS operator imports, and control-flow rewrites where targeted. - Adopt standalone if that is the decision. Convert eligible components to
standalone: trueand switch bootstrap tobootstrapApplication, applied the same way across every repo rather than app by app. - Align CI. Update the Node version in
.github/workflows/*.ymlto match the target major’s runtime floor. - Gate on a production build. Require
ng testandng build --configuration productionto pass before merge, because AOT-only template errors surface in the production build and never in dev.
Decisions to make before the first repo
Pin the exact target major. Schematics, the built-in control flow, and zoneless support differ between adjacent versions, so “latest” is not a specification. Angular 18 and Angular 19 are different migrations with different deprecated-API sets.
Decide once whether the upgrade also opts apps into standalone components or keeps NgModules. Making that call inconsistently across repos leaves a codebase that is half NgModules and half standalone, which costs more to maintain than committing to either.
Treat the production build as the real gate. Development builds pass on templates that ahead-of-time compilation rejects, so a repo can look upgraded and still fail in CI. Require the production build and the test suite before anything merges.
What the change actually looks like
The package.json delta for a single repo moving from Angular 16 to 18:
"dependencies": {
- "@angular/common": "^16.2.0",
- "@angular/compiler": "^16.2.0",
- "@angular/core": "^16.2.0",
- "@angular/forms": "^16.2.0",
- "@angular/platform-browser": "^16.2.0",
- "@angular/router": "^16.2.0",
- "zone.js": "~0.13.0"
+ "@angular/common": "^18.2.0",
+ "@angular/compiler": "^18.2.0",
+ "@angular/core": "^18.2.0",
+ "@angular/forms": "^18.2.0",
+ "@angular/platform-browser": "^18.2.0",
+ "@angular/router": "^18.2.0",
+ "zone.js": "~0.14.0"
},
"devDependencies": {
- "@angular/cli": "^16.2.0",
- "@angular/compiler-cli": "^16.2.0",
- "typescript": "~5.1.0"
+ "@angular/cli": "^18.2.0",
+ "@angular/compiler-cli": "^18.2.0",
+ "typescript": "~5.5.0"
},
+ "engines": { "node": ">=18.19.0" }
Mechanically small. The @ViewChild rewrites, the provideHttpClient swap, the CI Node bump, and the production-build verification sit behind it, and every one of them has to happen in every repo before the fleet is actually on the target major.
Running the same upgrade across every repo at once
The sequence above is mechanical, and it is identical in every repository, which is exactly the kind of work that does not need a person running it by hand in every repo. Tidra is an AI coding agent for both implementation and coordination of code changes across your organization.
For an Angular major upgrade, it reads package.json, angular.json, and tsconfig.json across the targeted repositories to find where each app sits, aligns the framework and peer dependencies to the target major, applies the in-tree migrations, updates the CI Node floor, and opens one pull request per repository. Each PR summarizes the migrations applied, the schematics that needed manual intervention, and the deprecated APIs still in use, so the repos that need a human are flagged instead of hidden inside a fleet-wide diff.
Tidra is one of the few migration tools built for the repository-count problem rather than the single-repo problem, and it delivers every change as a reviewable pull request rather than a direct commit.
A platform engineer at a large frontend org put it plainly:
“It’s the only way to easily make an upgrade across 300 repos at once.”
Keeping engineers in control of an automated upgrade
An Angular major upgrade is not deterministic. Third-party libraries lag the target major. Custom builders resist the schematic. Ahead-of-time compilation surfaces template errors that no tool can fully predict from the source. Any honest description of automating this has to account for the repos where the schematic does not apply cleanly.
That is why the model matters more than the automation. Nothing direct-commits. The change plan is reviewed and refined before any code is generated, the diffs are reviewed per repo, and every change lands as a pull request the existing CI validates and an engineer approves. When a generated change is wrong, the plan gets corrected before it reaches a single repository.
A technical lead at a customer we work with described the payoff:
“The ability to iterate on the plan before touching any code changes everything.”
The prompt, ready to adapt
The instruction that drives the upgrade, with version numbers edited to match the target before running:
Upgrade every Angular app from its current major version to Angular 18.
For each repository:
1. Update package.json Angular dependencies (@angular/core, @angular/cli,
@angular/common, @angular/router, etc.) to ^18.2.0.
2. Apply the equivalent of `ng update @angular/core@18 @angular/cli@18`
directly: update the Angular CLI/Material/CDK config in angular.json, the
compiler options in tsconfig.json, and polyfills per Angular 18's
migration schematics.
3. Replace deprecated APIs flagged in the Angular 18 release notes:
- Migrate @ViewChild({ static: false }) defaults
- Replace HttpClientModule imports with provideHttpClient where standalone is adopted
- Update RxJS operator imports to RxJS 7.8
4. Convert eligible components to standalone (standalone: true) and update
bootstrap to bootstrapApplication.
5. Update CI Node version in .github/workflows/*.yml to Node 20.x to match
Angular 18's runtime floor.
The number worth tracking
Every six months the gap widens by one more major. Frontends that were one major behind become two behind, and the reconciliation that was tractable this quarter compounds into next quarter’s migration project. The metric worth putting on the planning agenda is how many majors the fleet has fallen behind, and how much larger each deferred upgrade gets before the schematic stops applying at all.
Run this across every repo: tidra.ai/initiatives/angular-major-upgrade
FAQ
Can ng update run across multiple repositories at once?
No. The Angular CLI upgrades one repository at a time and requires a clean working tree with the previous major’s deprecations already resolved. Running it across an organization means executing that same sequence in every repository and reconciling each one’s version drift first.
How many Angular major versions can be upgraded at once? The CLI migrates one major at a time. A repository two majors behind has to complete the intermediate major’s migration before the target major’s schematics apply, so a multi-major jump is a sequence of upgrades rather than a single step.
What breaks most often when upgrading Angular across many repos?
Peer-dependency mismatches. Angular Material and CDK must move in lockstep with @angular/core, RxJS and zone.js carry version floors, and the Node engine floor rises with each major. Ahead-of-time template errors are the other common surprise, because they appear in the production build and never in dev.
Does every repo have to adopt standalone components during the upgrade? No. Standalone adoption is a separate decision from the version bump. It can be made repo by repo, but making it inconsistently across an org leaves a codebase split between NgModules and standalone, which is harder to maintain than either choice alone.
What does a coordinated Angular upgrade produce for review? One pull request per repository, each summarizing the migrations applied, the schematics that needed manual intervention, and the deprecated APIs still in use. Engineers review and merge. Nothing direct-commits.