Core upgrade: Angular 9 → Angular 20 (framework + build config)#417
Core upgrade: Angular 9 → Angular 20 (framework + build config)#417tobydrinkall wants to merge 2 commits into
Conversation
Co-Authored-By: Toby Drinkall <toby.drinkall@cognition.ai>
Co-Authored-By: Toby Drinkall <toby.drinkall@cognition.ai>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
| enableProdMode(); | ||
| } | ||
|
|
||
| platformBrowserDynamic().bootstrapModule(AppModule) | ||
| platformBrowser().bootstrapModule(AppModule) |
There was a problem hiding this comment.
📝 Info: Bootstrap method changed from JIT-capable to AOT-only
The switch from platformBrowserDynamic().bootstrapModule() to platformBrowser().bootstrapModule() removes the JIT compiler from the bundle. This works correctly because Angular CLI uses AOT by default (mandatory since Angular 13), and Ivy compiles decorators into static fields at build time. However, this means any future attempt to use JIT compilation (e.g., runtime template compilation via Compiler.compileModuleAsync()) would silently fail. Since the app doesn't use JIT features, this is safe and produces a slightly smaller bundle, but it's a subtle behavioral contract change worth noting.
(Refers to lines 2-11)
Was this helpful? React with 👍 or 👎 to provide feedback.
| "moduleResolution": "node", | ||
| "emitDecoratorMetadata": true, | ||
| "module": "es2022", | ||
| "moduleResolution": "bundler", |
There was a problem hiding this comment.
🚩 Deep rxjs imports rely on rxjs-compat with new moduleResolution
The tsconfig.json changes moduleResolution from "node" to "bundler". Two files still use legacy deep imports: import { Subscription } from 'rxjs/Subscription' at src/app/item-details/item-details.component.ts:4 and src/app/user/user.component.ts:4, plus import { Observable } from 'rxjs/Observable' at src/app/shared/services/hackernews-api.service.ts:2. These resolve via the rxjs-compat package. Since rxjs 6.6.7 has no exports field in its package.json, TypeScript with moduleResolution: "bundler" falls back to file-based resolution, which should find the compat shims. However, if rxjs-compat is ever removed, all three imports would break. The canonical imports (from 'rxjs') are already used in src/app/feeds/feed/feed.component.ts:2-3, making the inconsistency evident.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Intentional for this foundational wave — rxjs-compat is kept so the deep imports keep resolving. The follow-up RxJS wave (based on this branch) upgrades to rxjs 7, removes rxjs-compat, and rewrites all deep imports to from 'rxjs'.
| "@angular/common": "^20.0.0", | ||
| "@angular/compiler": "^20.0.0", | ||
| "@angular/core": "^20.0.0", | ||
| "@angular/forms": "^20.0.0", | ||
| "@angular/platform-browser": "^20.0.0", | ||
| "@angular/router": "^20.0.0", | ||
| "@angular/service-worker": "^20.0.0", | ||
| "rxjs": "~6.6.7", | ||
| "rxjs-compat": "^6.6.7", | ||
| "tslib": "^2.6.0", | ||
| "unfetch": "^4.1.0", | ||
| "zone.js": "~0.10.2" | ||
| "zone.js": "~0.15.0" |
There was a problem hiding this comment.
📝 Info: Removed @angular/animations dependency is an optional peer dep
The PR removes @angular/animations from package.json dependencies. @angular/platform-browser lists it as a peer dependency, but the package-lock.json confirms it's marked optional: true in peerDependenciesMeta. A codebase-wide grep confirms no imports of @angular/animations, BrowserAnimationsModule, or NoopAnimationsModule anywhere in src/. So this removal is safe. If animations support is needed in the future, it must be re-added explicitly.
Was this helpful? React with 👍 or 👎 to provide feedback.
| "module": "es2022", | ||
| "moduleResolution": "bundler", | ||
| "experimentalDecorators": true, | ||
| "importHelpers": true, | ||
| "target": "es2015", | ||
| "target": "es2022", | ||
| "useDefineForClassFields": false, |
There was a problem hiding this comment.
📝 Info: Removed emitDecoratorMetadata may affect non-Angular DI libraries
The tsconfig.json removes emitDecoratorMetadata: true. Angular with Ivy does not need this setting — it uses its own compilation pipeline to generate metadata. However, if any third-party library in the project relies on TypeScript's reflect-metadata for decorator-based dependency injection (e.g., some testing utilities or IoC containers), those would break silently. A quick check of the codebase shows only Angular decorators are used, so this is safe for the current codebase.
Was this helpful? React with 👍 or 👎 to provide feedback.
E2E test results — full migration (waves #417 + #418 + #419 + #420 merged locally)Merged all four wave branches into a local integration branch, ran lint/build/test, then served the app and clicked through the golden paths. Results
Screenshots
Shell evidence (integration branch)Notes: merging #418 after #419/#420 produces a |
Summary
Foundational wave of the Angular 9 → 20 upgrade. Jumps the core framework and build toolchain directly to Angular 20 (TypeScript 5.8, zone.js 0.15, tslib 2), keeping code changes to the minimum needed to make
ng build(dev + production, incl. service worker) compile.Key decisions/changes:
@angular/platform-browser-dynamicno longer ships with v20 —main.tsbootstraps viaplatformBrowser()from@angular/platform-browser; dropped unused@angular/animations.standalone: falseto all 12 declarables so existing NgModules keep working (NgModule → standalone conversion is deferred to a later wave).angular.json: removed defunct options (extractCss,aot,es5-era flags,defaultProject),browserTarget→buildTarget; test target dropsmain/src/test.ts(CLI auto-discovers specs now).karma-coverage-istanbul-reporter→karma-coverage; jasmine/karma stack bumped for Angular 20's karma builder.target/module es2022,moduleResolution: bundler,skipLibCheck(needed forunfetch's d.ts),useDefineForClassFields: false.yarn.lock, committedpackage-lock.json).Deliberately deferred to follow-up waves: RxJS 7 + removal of
rxjs-compat/deep imports, deprecatedsubscribe(next, err, complete)signatures, tslint→eslint migration, protractor removal, adding unit tests, Sass@import/division deprecations.Verified:
npm run buildandng build --configuration productionsucceed on Node 22.Link to Devin session: https://app.devin.ai/sessions/7d72297591cd4aaa8bfa196a17d071fa
Requested by: @tobydrinkall