Skip to content

feature: modernize the app from Angular 9 to Angular 19 - #569

Open
neilspalding-hub wants to merge 19 commits into
masterfrom
devin/1785919235-angular-modernization
Open

feature: modernize the app from Angular 9 to Angular 19#569
neilspalding-hub wants to merge 19 commits into
masterfrom
devin/1785919235-angular-modernization

Conversation

@neilspalding-hub

@neilspalding-hub neilspalding-hub commented Aug 5, 2026

Copy link
Copy Markdown

Summary

Angular 9 → 19 (one major at a time via ng update), then the app itself was modernized: standalone bootstrap, HttpClient, RxJS 7, ESLint, real unit tests, and GitHub Actions.

Data layer. The hand-rolled lazyFetch/unfetch service is gone; HackerNewsAPIService now injects HttpClient and returns typed observables. The old node-hnapi.herokuapp.com host still serves feeds and items but returns 404 for /user/*, so the base URL moved to environment.hnApiBaseUrl = https://api.hnpwa.com/v0. That API is a drop-in match for the Story/User models with one gap: a poll item does not carry its options. Poll option ids therefore come from the official HN API (environment.hnOfficialApiBaseUrl) and the options themselves still come from the feed API, which returns them already shaped like PollResult:

fetchItemContent(id) -> get(`${baseUrl}/item/${id}.json`)
  |> type === 'poll' ? fetchPollResults(story) : of(story)

fetchPollResults(story) -> get(`${officialApiBaseUrl}/item/${story.id}.json`)   // { parts: [id, ...] }
  |> forkJoin(parts.map(part => get(`${baseUrl}/item/${part}.json`)))
  |> { ...story, poll, poll_votes_count: sum(points) }

Architecture. app.module.ts, CoreModule, SharedComponentsModule, PipesModule and the item/user feature modules are deleted. main.ts uses bootstrapApplication with provideRouter, provideHttpClient and provideServiceWorker; item-details and user routes stay lazy via loadComponent. Templates use built-in control flow (@if/@for), components use inject(), and every route subscription is now a single takeUntilDestroyed() pipeline instead of leaked typeSub/pageSub fields — FeedComponent combines route data and params so a feed type change and a page change can no longer race:

combineLatest([route.data, route.params])
  |> tap(([data, params]) => { feedType = data.feedType; pageNum = +params.page })
  |> switchMap(() => fetchFeed(feedType, pageNum) |> catchError(...))
  |> takeUntilDestroyed(destroyRef)

SettingsService also unsubscribed nothing before: it passed a fresh .bind(this) to removeEventListener, so the prefers-color-scheme listener was never removed. It now holds one bound handler.

Tooling. TSLint/Codelyzer/Protractor and e2e/ are gone, replaced by angular-eslint (eslint.config.js); lint passes clean, which required real fixes — keyboard handlers on click-only elements and the item selector renamed to app-item. Karma runs ChromeHeadlessNoSandbox by default and there are now 7 specs (HackerNewsAPIService URL/poll mapping via provideHttpClientTesting, FeedComponent rendering/paging/error states). .travis.yml (Node 6.9) is replaced by .github/workflows/ci.yml: Node 20, npm ci → lint → test → build, plus a Firebase deploy job on master that accepts either FIREBASE_SERVICE_ACCOUNT or FIREBASE_TOKEN.

Two config fixes fall out of the Angular 18 application-builder migration: firebase.json now publishes dist/angular-hnpwa/browser, and ng build defaults to the production configuration so the service worker is actually emitted.

Verification

Lint, the 7 unit tests and the production build (with ngsw.json + ngsw-worker.js) all pass locally, and the dev server renders live HN data — feeds, item comments, polls with vote bars, and user profiles.

feed

poll item

Notes

  • package-lock.json is now tracked (needed by npm ci) and the stale yarn.lock was removed.
  • No e2e framework was added in place of Protractor; the e2e script was dropped.

Link to Devin session: https://app.devin.ai/sessions/8c61275406d040b58467ffa58183ab15
Requested by: @neilspalding-hub


Devin Review

Status Commit
⚪ Not started

Run Devin Review

💡 Connect your GitHub account to enable automatic code reviews.

Open in Devin Review (Staging)

devin-ai-integration Bot and others added 18 commits August 5, 2026 08:40
Co-Authored-By: Neil Spalding <neil.spalding@cognition.ai>
Co-Authored-By: Neil Spalding <neil.spalding@cognition.ai>
…ooling

Co-Authored-By: Neil Spalding <neil.spalding@cognition.ai>
Co-Authored-By: Neil Spalding <neil.spalding@cognition.ai>
Co-Authored-By: Neil Spalding <neil.spalding@cognition.ai>
Co-Authored-By: Neil Spalding <neil.spalding@cognition.ai>
Co-Authored-By: Neil Spalding <neil.spalding@cognition.ai>
Co-Authored-By: Neil Spalding <neil.spalding@cognition.ai>
Co-Authored-By: Neil Spalding <neil.spalding@cognition.ai>
Co-Authored-By: Neil Spalding <neil.spalding@cognition.ai>
Co-Authored-By: Neil Spalding <neil.spalding@cognition.ai>
…yer and rxjs 7 cleanup

Co-Authored-By: Neil Spalding <neil.spalding@cognition.ai>
Co-Authored-By: Neil Spalding <neil.spalding@cognition.ai>
Co-Authored-By: Neil Spalding <neil.spalding@cognition.ai>
Co-Authored-By: Neil Spalding <neil.spalding@cognition.ai>
… path

Co-Authored-By: Neil Spalding <neil.spalding@cognition.ai>
…rowserslist

Co-Authored-By: Neil Spalding <neil.spalding@cognition.ai>
…n stack

Co-Authored-By: Neil Spalding <neil.spalding@cognition.ai>
@devin-ai-integration

Copy link
Copy Markdown

🤖 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

Co-Authored-By: Neil Spalding <neil.spalding@cognition.ai>
@devin-ai-integration

Copy link
Copy Markdown

End-to-end verification passed

Verified at runtime by Devin: toolchain in the shell, the production build served as a static PWA, and the full UI flow on the dev server against the live HN API. All 14 assertions passed; no functional defects found.

Service worker activated (the headline change)

ng build now defaults to production, so ngsw.json and ngsw-worker.js are emitted and the worker actually registers:

Service worker activated and running

DevTools shows ngsw-worker.js at "#1 activated and is running" with a complete Install → Wait → Activate cycle. The dev server registers no worker at all, since provideServiceWorker is gated on environment.production.

To prove offline support decisively the static server was killed and a route not yet visited in that tab was loaded — the app shell and jobs feed still rendered, so index.html and the bundles came from the SW cache:

Jobs feed served offline

Poll rendering — proves the two-API data path

Poll with proportional vote bars

/item/126809 renders all 3 options with bars proportional to votes (73/49/179 of 301 → 24.3% / 16.2% / 59.1%, matching the measured widths). Option IDs can only come from the firebaseio parts array and their points from the subsequent forkJoin of hnpwa calls — if either leg broke, the block would be empty or the widths NaN%.

Toolchain
Check Result
npm run lint All files pass linting
npm test -- --watch=false Executed 7 of 7 SUCCESS
npm run build exit 0, emits ngsw.json + ngsw-worker.js in dist/angular-hnpwa/browser

The build emits Sass deprecation warnings (darken(), @import). Warnings only today, but they become hard errors on Dart Sass 3.0.0.

App behaviour against the live HN API
  • All five feeds (news/newest/show/ask/jobs) render live data; jobs shows its YC header
  • Paging continuity: page 1 ends at rank 30, More › starts page 2 at 31 (not 1), ‹ Prev returns to 1–30
  • Comments render; [-] collapse works by mouse and by Tab+Enter — directly exercising the new (keyup.enter) a11y handler
  • /user/pg renders id, 157316 karma and creation date
  • Back buttons on item and user pages navigate back (they live in .mobile markup, so this needed a <768px viewport)
  • Settings cog opens via Enter, closes via Tab-to-× + Enter
  • Zero console errors across every flow
Settings persistence

Night + AMOLED themes, font size 28 and list spacing 40 all apply and survive a full reload, confirming localStorage read-back still works under the new standalone bootstrapApplication startup.

Settings persisted after reload

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