Skip to content

Modernize Angular 9 PWA to Angular 17 (standalone, ESLint, Playwright, GitHub Actions)#298

Open
devin-ai-integration[bot] wants to merge 2 commits into
masterfrom
devin/1780673327-angular-17-modernization
Open

Modernize Angular 9 PWA to Angular 17 (standalone, ESLint, Playwright, GitHub Actions)#298
devin-ai-integration[bot] wants to merge 2 commits into
masterfrom
devin/1780673327-angular-17-modernization

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jun 5, 2026

Copy link
Copy Markdown

Summary

Full modernization of this Hacker News PWA from Angular 9 → Angular 17, covering all 11 requested areas: framework/TypeScript/RxJS upgrades, TSLint→ESLint, Protractor→Playwright, Travis→GitHub Actions, standalone components, new control-flow templates, PWA/service-worker consolidation, and dependency cleanup.

Approach note: A strict incremental ng update (9→10→…→17) was not viable in this environment — Angular CLI 9 requires Node 10/12/14, but only Node 18/20/22 are available, and each major step needs a different Node + reinstalls of long-deprecated packages. Instead the workspace was rebuilt directly at Angular 17 and the (small) source ported, applying every transformation the incremental path would have produced. The end state is identical to what the incremental upgrade yields.

Everything is verified locally: prod build, ng lint, 6 unit tests (headless Chrome), and 2 Playwright e2e tests all pass.

What changed

Dependencies (package.json)

  • @angular/* ~9.0.1^17.3.0; @angular/cli + @angular-devkit/build-angular^17.3
  • typescript ~3.7.5~5.4.2; rxjs ~6.5.4~7.8; zone.js ~0.10~0.14; tslib ^1^2
  • Removed rxjs-compat, unfetch, node-fetch, protractor, tslint, codelyzer, @types/jasminewd2
  • Added @angular-eslint/*, @typescript-eslint/*, eslint, @playwright/test; bumped Karma/Jasmine to v6/v5

Architecture — standalone (no NgModules)

  • Deleted all *.module.ts (AppModule, CoreModule, feature + shared modules)
  • main.ts now uses bootstrapApplication(AppComponent, appConfig)
  • New app.config.ts provides router + service worker; routing split into app.routes.ts with lazy loadChildren for item-details.routes.ts and user.routes.ts
  • Every component is standalone: true with explicit imports

RxJS 7 + native fetch

  • Barrel imports (from 'rxjs', from 'rxjs/operators'); object-form subscribe({ next, error, complete })
  • lazyFetch<T>() in hackernews-api.service.ts uses the browser's native fetch (no unfetch)

Templates — Angular 17 control flow

  • *ngIf/*ngFor/*ngSwitch@if/@for (… ; track …)
  • Added keyboard handlers + role/tabindex to click targets to satisfy @angular-eslint a11y rules

Tooling / CI / PWA

  • ESLint flat-less config (.eslintrc.json via @angular-eslint/builder:lint); tslint.json removed
  • Protractor e2e/ replaced with playwright.config.ts + e2e/app.spec.ts
  • .travis.yml removed; .github/workflows/ci.yml added (install → lint → unit tests → prod build, plus a master-only Firebase Hosting deploy job gated on a FIREBASE_SERVICE_ACCOUNT secret)
  • Consolidated on @angular/service-worker (ngsw-config.json + provideServiceWorker()), dropping the Workbox approach; manifest.jsonmanifest.webmanifest
  • Removed dead src/environments/* (replaced by isDevMode()), polyfills.ts, test.ts; updated README; switched to package-lock.json

Notes for reviewers

  • Added content: string to the Story model so strictTemplates accepts the item body binding.
  • CI runs unit tests via npm run test:ci (ChromeHeadlessCI, --no-sandbox); GitHub-hosted ubuntu-latest ships Chrome, so no extra setup step is needed.
  • The Sass /-division deprecation warnings are pre-existing and non-blocking.

Link to Devin session: https://app.devin.ai/sessions/ff0508e294a3458e8527269660a3f20e
Requested by: @eddiemattout-cog


Devin Review

Status Commit
🟢 Reviewed 08c9c2b
Open in Devin Review (Staging)

…laywright, GitHub Actions)

Co-Authored-By: Eddie Mattout <eddie.mattout@cognition.ai>
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@staging-devin-ai-integration staging-devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 potential issues.

View 4 additional findings in Devin Review.

Open in Devin Review (Staging)
Debug

Playground

Comment thread src/app/shared/models/story.ts
Comment thread src/app/shared/services/settings.service.ts Outdated
Comment thread src/app/feeds/feed/feed.component.html Outdated
You can also get a job at a YC startup through <a href="https://triplebyte.com/?ref=yc_jobs">Triplebyte</a>.
</p>
}
@if (feedType !== 'new') {

@staging-devin-ai-integration staging-devin-ai-integration Bot Jun 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Removed feedType !== 'new' guard on <ol> is a no-op change

The old template had <ol *ngIf="feedType !== 'new'" which was removed in the new template. This looks like a behavioral change, but examining the route configuration at src/app/app.routes.ts:10, the feed type for the 'newest' route is 'newest' (not 'new'), and no route ever sets feedType to 'new'. Therefore the old condition was always true and the <ol> was always rendered. Removing it doesn't change behavior. The original condition was likely a latent bug (should have been 'newest' if intended to hide the list for that feed).

Open in Devin Review (Staging)

Was this helpful? React with 👍 or 👎 to provide feedback.

Debug

Playground

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 08c9c2b — removed the always-true @if (feedType !== 'new') wrapper and render the <ol> directly. I kept runtime behavior identical (the <ol> already rendered for every feed type) rather than changing it to 'newest', to avoid altering existing behavior during the modernization.

…dead condition)

Co-Authored-By: Eddie Mattout <eddie.mattout@cognition.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant