[Spike] Reactive query/cache layer for lesson catalog + progress (evaluate options, incl. TanStack) #305

Open
opened 2026-07-01 15:29:12 +00:00 by spikerj · 0 comments
Owner

Problem

Data fetching for the browse surfaces relies on Angular HttpClient + coarse service-worker HTTP caching. There's no in-app reactive cache, so we don't get request de-duplication, stale-while-revalidate in the app layer, or cache invalidation (e.g. refresh progress after completing a lesson).

As catalog browsing/filtering/pagination becomes a primary flow, this shows up as redundant refetches, manual loading/error boilerplate, and stale progress after mutations.

Current state

  • provideHttpClient(withInterceptorsFromDi()); lesson data via HttpClient.
  • Service worker (ngsw-config.json) caches api-lessons-catalog (maxAge 7d) and api-lessons-progress (maxAge 10m) at the HTTP layer only — not reactive, no dedupe, no invalidation.
  • Endpoints that would benefit: GET /api/Lessons (paginated, filters: language/category/difficulty/searchTerm/skip/take/sort) and GET /api/Lessons/progress.
  • No query/cache lib present (no TanStack/Apollo/urql/SWR).
  • Out of scope for caching: GET /api/Lessons/{n}/attempt — each attempt is randomized/unique per request and intentionally uncacheable; it must be explicitly excluded from any cache layer.

Why a spike (not a commit-to-library)

The obvious pick, TanStack Query's Angular adapter (@tanstack/angular-query-experimental), is still experimental as of 2026-07 — its README warns breaking changes can land in minor and patch releases, and there are open maintainer-activity concerns on the stable-release tracker. That conflicts with the "won't break anything" bar, so we should evaluate before adopting.

Spike deliverables

Evaluate and recommend the lowest-risk, locally-controlled option for a reactive catalog/progress cache:

  1. In-house signals + rxjs cache serviceshareReplay/signal-backed store, manual invalidation. Zero new deps, fully controlled, Angular-21 signals-native. (Likely default recommendation.)
  2. @ngrx/signals (SignalStore) with entity caching — first-party-adjacent, stable, signals-first.
  3. @tanstack/angular-query-experimental, version-pinned — most features, but experimental; only if the caveats are acceptable and the version is locked.

Produce: a short comparison (bundle cost, stability, testing/SSR story, invalidation ergonomics), a recommendation, and a tiny proof-of-concept on the catalog list + progress refresh-after-completion.

Why the eventual change is non-breaking

  • Whatever we pick is additive — introduced on the catalog/progress flows only; existing HttpClient usage elsewhere is untouched.
  • The attempt endpoint stays on the direct path (explicitly not cached).

Acceptance criteria (spike)

  • Written recommendation with the tradeoff table + chosen option.
  • POC: catalog list de-dupes/caches queries by filter key; completing a lesson invalidates and refreshes progress without a full reload.
  • Confirmed compatibility with Angular 21.2 and SSR/hydration; no regression to existing HttpClient flows.
  • Follow-up implementation ticket(s) opened from the recommendation.

Priority

Medium — pick up when catalog browse/filter/pagination becomes a primary flow. Coordinate with the Angular v22/Nx upgrade epic (#219) since the choice interacts with the framework version.

(Local-hosting only — no CDN.)

## Problem Data fetching for the browse surfaces relies on Angular `HttpClient` + coarse **service-worker** HTTP caching. There's no in-app reactive cache, so we don't get request de-duplication, stale-while-revalidate in the app layer, or cache invalidation (e.g. refresh progress after completing a lesson). As catalog browsing/filtering/pagination becomes a primary flow, this shows up as redundant refetches, manual loading/error boilerplate, and stale progress after mutations. ## Current state - `provideHttpClient(withInterceptorsFromDi())`; lesson data via `HttpClient`. - Service worker (`ngsw-config.json`) caches `api-lessons-catalog` (maxAge 7d) and `api-lessons-progress` (maxAge 10m) at the HTTP layer only — not reactive, no dedupe, no invalidation. - Endpoints that would benefit: `GET /api/Lessons` (paginated, filters: language/category/difficulty/searchTerm/skip/take/sort) and `GET /api/Lessons/progress`. - No query/cache lib present (no TanStack/Apollo/urql/SWR). - **Out of scope for caching:** `GET /api/Lessons/{n}/attempt` — each attempt is randomized/unique per request and intentionally uncacheable; it must be explicitly excluded from any cache layer. ## Why a spike (not a commit-to-library) The obvious pick, TanStack Query's Angular adapter (`@tanstack/angular-query-experimental`), is **still experimental as of 2026-07** — its README warns breaking changes can land in **minor and patch** releases, and there are open maintainer-activity concerns on the stable-release tracker. That conflicts with the "won't break anything" bar, so we should evaluate before adopting. ## Spike deliverables Evaluate and recommend the **lowest-risk, locally-controlled** option for a reactive catalog/progress cache: 1. **In-house signals + rxjs cache service** — `shareReplay`/signal-backed store, manual invalidation. Zero new deps, fully controlled, Angular-21 signals-native. (Likely default recommendation.) 2. **`@ngrx/signals` (SignalStore)** with entity caching — first-party-adjacent, stable, signals-first. 3. **`@tanstack/angular-query-experimental`, version-pinned** — most features, but experimental; only if the caveats are acceptable and the version is locked. Produce: a short comparison (bundle cost, stability, testing/SSR story, invalidation ergonomics), a recommendation, and a tiny proof-of-concept on the catalog list + progress refresh-after-completion. ## Why the eventual change is non-breaking - Whatever we pick is **additive** — introduced on the catalog/progress flows only; existing `HttpClient` usage elsewhere is untouched. - The attempt endpoint stays on the direct path (explicitly not cached). ## Acceptance criteria (spike) - [ ] Written recommendation with the tradeoff table + chosen option. - [ ] POC: catalog list de-dupes/caches queries by filter key; completing a lesson invalidates and refreshes progress without a full reload. - [ ] Confirmed compatibility with Angular 21.2 and SSR/hydration; no regression to existing HttpClient flows. - [ ] Follow-up implementation ticket(s) opened from the recommendation. ## Priority Medium — pick up when catalog browse/filter/pagination becomes a primary flow. Coordinate with the Angular v22/Nx upgrade epic (#219) since the choice interacts with the framework version. _(Local-hosting only — no CDN.)_
spikerj added the enhancement label 2026-07-01 15:29:12 +00:00
Sign in to join this conversation.