feat: upgrade Angular 9 → 20 (Wave 1 — core framework + build config)#426
feat: upgrade Angular 9 → 20 (Wave 1 — core framework + build config)#426tobydrinkall wants to merge 3 commits into
Conversation
Incrementally upgraded through all major versions: 9→10→11→12→13→14→15→16→17→18→19→20. Package versions reached: - @angular/* packages: 20.3.25 - @angular/cli: 20.3.31 - @angular-devkit/build-angular: 20.3.31 - TypeScript: 5.8.3 - zone.js: 0.15.0 - tslib: 2.6.0 - rxjs: 6.5.4 (unchanged, Wave 2 handles RxJS 7 migration) Config changes: - tsconfig.json: target→ES2022, module→es2022, added angularCompilerOptions, removed emitDecoratorMetadata/experimentalDecorators (Ivy does not need them) - angular.json: removed deprecated extractCss, removed defaultProject - browserslist → .browserslistrc with modern browser targets - polyfills.ts: simplified, zone.js/dist/zone → zone.js - test.ts: zone.js/dist/zone-testing → zone.js/testing - Removed rxjs-compat (rxjs/Observable→rxjs, rxjs/Subscription→rxjs) - Removed yarn.lock, added package-lock.json (npm) Code changes (minimum for compilation): - All components/pipes: added standalone: false (Angular 19+ default) - hackernews-api.service.ts: fixed TS7030 (not all code paths return) 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:
|
| if (cancelToken) { return undefined; } | ||
| return res.json() | ||
| .then(data => { | ||
| fetchObserver.next(data); | ||
| fetchObserver.complete(); | ||
| }); |
There was a problem hiding this comment.
📝 Info: The lazyFetch refactoring is logically equivalent to the original
The refactored lazyFetch function (hackernews-api.service.ts:53-58) inverts the condition from if (!cancelToken) { ... } to if (cancelToken) { return undefined; } followed by the main logic. This is a correct early-return transformation that preserves identical behavior. The explicit return undefined also satisfies the new noImplicitReturns: true tsconfig setting (tsconfig.json:10).
Was this helpful? React with 👍 or 👎 to provide feedback.
| standalone: false, | ||
| selector: 'app-root', | ||
| templateUrl: './app.component.html', | ||
| styleUrls: ['./app.component.scss'] |
There was a problem hiding this comment.
📝 Info: All components use deprecated 'styleUrls' (plural) instead of 'styleUrl'
All 11 components in the project use styleUrls: ['./foo.component.scss'] (e.g., src/app/app.component.ts:13). Angular 17+ deprecated styleUrls in favor of styleUrl (singular string) when there is only one stylesheet. While styleUrls still works, since this PR is upgrading to Angular 20, it would be a good opportunity to modernize these to styleUrl. Similarly, templateUrl could be checked against the newer template inline option, though templateUrl remains fully supported.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
styleUrls → styleUrl modernization is scoped to Wave 3 (component/template fixes, child session on branch upgrade/component-template).
| "protractor": "~5.4.0", | ||
| "ts-node": "~7.0.0", | ||
| "tslint": "~5.15.0", | ||
| "typescript": "~3.7.5" | ||
| "typescript": "~5.8.3" |
There was a problem hiding this comment.
🚩 Incomplete upgrade: dev tooling dependencies left at very old versions
While Angular and TypeScript were upgraded, several dev dependencies remain at versions that are incompatible with Angular 20 and TypeScript 5.8: protractor (~5.4.0, deprecated and removed from Angular CLI), tslint (~5.15.0, deprecated in favor of ESLint), codelyzer (^5.1.2, depends on tslint), karma (~4.1.0), and @types/jasmine (~3.3.8). The angular.json still references @angular-devkit/build-angular:tslint for linting (angular.json:106) and @angular-devkit/build-angular:protractor for e2e (angular.json:119), both of which were removed from the Angular CLI build-angular package. Running ng lint or ng e2e will fail.
(Refers to lines 34-47)
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Intentional — TSLint→ESLint migration, Karma/Jasmine updates, and Protractor removal are scoped to Wave 4 (child session on branch upgrade/test-lint). The lint/e2e builders in angular.json will be updated in that wave.
| "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.
🚩 zone.js version may not match Angular 20 requirements
The PR specifies zone.js: ~0.15.0 (package.json:27). Angular versions typically ship with a specific compatible zone.js version. Angular 18 used zone.js 0.14.x, and Angular 19+ made zone.js optional. For Angular 20, the required zone.js version should be verified against Angular's peer dependencies to ensure compatibility. The polyfill import path change from zone.js/dist/zone to zone.js (src/polyfills.ts:9) and zone.js/dist/zone-testing to zone.js/testing (src/test.ts:3) are correct for zone.js 0.11+.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
zone.js ~0.15.0 is correct — verified via npm show @angular/core@20.3.25 peerDependencies which specifies "zone.js": "~0.15.0".
- angular.json: builder browser → application, main → browser - Polyfills moved to angular.json array (zone.js), removed polyfills.ts - Removed deprecated options: aot, vendorChunk, buildOptimizer (automatic in esbuild) - browserTarget → buildTarget in serve/extract-i18n configs - serviceWorker + ngswConfigPath → serviceWorker: 'ngsw-config.json' - Test polyfills also moved to angular.json array - tsconfig.app.json: removed polyfills.ts from files array Co-Authored-By: Toby Drinkall <toby.drinkall@cognition.ai>
| "@angular/router": "~20.3.25", | ||
| "@angular/service-worker": "~20.3.25", | ||
| "node-fetch": "^2.6.0", | ||
| "rxjs": "~6.5.4", |
There was a problem hiding this comment.
📝 Info: RxJS 6.5 is old but still within Angular 20's peer dependency range
The PR keeps rxjs at ~6.5.4 while upgrading Angular to 20.x. Angular 20.3.25's @angular/core declares peerDependencies of rxjs: ^6.5.3 || ^7.4.0 (verified in package-lock.json), so this is technically compatible. However, RxJS 6.5 is quite old (released 2019) and lacks many bug fixes and features from RxJS 7.x. The removal of rxjs-compat (line 25 of old package.json) is correct since the codebase already uses RxJS 6-style imports. Consider upgrading to RxJS 7.x for long-term maintainability.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Acknowledged — RxJS 7 upgrade is handled in Wave 2 (child session on upgrade/rxjs-ts-api).
| "builder": "@angular-devkit/build-angular:application", | ||
| "options": { | ||
| "aot": true, | ||
| "outputPath": "dist/angular-hnpwa", | ||
| "index": "src/index.html", | ||
| "main": "src/main.ts", | ||
| "polyfills": "src/polyfills.ts", | ||
| "browser": "src/main.ts", |
There was a problem hiding this comment.
🚩 Migration to application builder changes output directory structure
The builder was changed from @angular-devkit/build-angular:browser to @angular-devkit/build-angular:application (angular.json:18), and the entry point key changed from main to browser (line 22). The application builder (esbuild-based) produces a different output directory structure than the old browser builder (webpack-based): output goes into dist/angular-hnpwa/browser/ subdirectory rather than directly into dist/angular-hnpwa/. Any deployment scripts, CI pipelines (.travis.yml), or Firebase hosting config (firebase.json) that reference the old output path may need updating.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Good catch — updated firebase.json public path from "dist" to "dist/angular-hnpwa/browser" to match the application builder's output structure. Fixed in ae50b40.
…base output path - tsconfig.spec.json: removed src/polyfills.ts from files array (file was deleted) - firebase.json: updated public path to dist/angular-hnpwa/browser (application builder output) Co-Authored-By: Toby Drinkall <toby.drinkall@cognition.ai>
| @@ -129,6 +128,5 @@ | |||
| } | |||
There was a problem hiding this comment.
🚩 Angular lint/e2e architect targets reference removed/deprecated builders
The angular.json still contains lint and e2e architect targets using @angular-devkit/build-angular:tslint and @angular-devkit/build-angular:protractor respectively. Both builders were removed from @angular-devkit/build-angular after Angular 12. Running ng lint or ng e2e will fail with Angular 20's build tooling. These targets should either be removed or migrated to their modern equivalents (ESLint and Cypress/Playwright).
(Refers to lines 104-128)
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Intentional deferral — TSLint→ESLint and Protractor removal (including these angular.json targets) are scoped to Wave 4 (child session on branch upgrade/test-lint). See earlier reply.
Summary
Foundational PR for the Angular 9→20 migration. Incrementally upgraded through every major version (9→10→11→12→13→14→15→16→17→18→19→20), verifying
ng buildat each step.Final versions
@angular/*@angular/cli@angular-devkit/build-angulartypescriptzone.jstslibrxjsKey config changes
angular.json— migrated to modernapplicationbuilder (esbuild):Removed deprecated options:
extractCss,aot,vendorChunk,buildOptimizer,defaultProject. Service worker config consolidated to"serviceWorker": "ngsw-config.json".tsconfig.json— modernized to Angular 20 defaults:browserslist→.browserslistrc— dropped IE9-11 targeting, modern browser list only.polyfills.ts— removed entirely;zone.jspolyfill now inangular.jsonarray.test.ts—zone.js/dist/zone-testing→zone.js/testing; test polyfills also moved toangular.json.Minimum code changes for compilation
standalone: false(Angular 19+ defaults standalone totrue).hackernews-api.service.ts: fixed old-style RxJS imports (rxjs/Observable→rxjs,rxjs/Subscription→rxjs), removedrxjs-compatdependency, fixed TS7030 (not all code paths return a value).Remaining waves (separate PRs rebased off this branch)
Link to Devin session: https://app.devin.ai/sessions/7122dd45070a47c68a2b8a07e07c3ba7
Requested by: @tobydrinkall
Devin Review