# ng-signals-template A minimal Angular 22 starter: signals for state, `resource()` for async data, and a `RemoteData` type that makes "loading but has an error," "success with no value," and similar illegal combinations impossible to construct. One worked feature (`users/`) shows the whole pattern end to end, including an action (click a user → see their details → go back). It's extracted from a larger reference app — the "POC" referenced throughout this document — which shows the same ideas grown up to production scale (multi-context DDD, enforced architecture boundaries, i18n, a real design system, generated API clients). This template deliberately keeps only the part of that setup useful from day one, and documents exactly where to reach for the rest as a project grows. ## Running it ```bash npm start # ng serve npm test # ng test (Vitest) ``` ## What's here - **`RemoteData`** (`shared/application/remote-data.ts`) — a 4-variant union (`Loading | Empty | Failure | Success`) plus `fromResource()`, which projects Angular's own `resource()` into one. No store, no reducer — `resource()` already holds the async state; `RemoteData` just normalizes it for exhaustive rendering. - **``** (`shared/ui/molecules/async.component.ts`) — a `@switch` over all 4 states: a spinner while loading, an empty message, a failure message with a retry button, or your projected content on success. Reused by both fetches in `users/`. - **``** (`shared/ui/templates/page-shell.component.ts`) — a heading plus one content slot. That's it. - **`users/`** — one feature context, laid out the same way a bigger one would be: `domain/` (pure types, no Angular import), `infrastructure/` (the only file allowed to call `fetch`), `application/` (composes infrastructure + `resource()` — this is also exactly where a real store would slot in later), `ui/` (organisms + the page). Clicking a user in the list sets one plain `signal` on the page, which swaps in a `UserDetailComponent` that does its own independent fetch. - Path aliases `@shared/*` and `@users/*` (see `tsconfig.json`) instead of relative `../../` imports, one per context — add one per new context you create. - Tests are BDD-style (`describe`/`it`, one `expect` per `it`) and black-box: they assert on rendered DOM and emitted events, never on a component's private fields, so a test never breaks just because an internal was refactored. ## What's deliberately not here (vs. the POC) | Missing | Why | | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | Elm-style store (`createStore`/Model-Msg-reduce) | Not needed until a page's state has more than a couple of interacting fields — see Growth path below. | | i18n (`$localize` + translation file) | POC-specific requirement (a Dutch app shipping English too); irrelevant for a single-locale starter. | | CIBG Huisstijl theming / token bridge | The POC's specific design system; a starter has no house style to vendor yet. | | `dependency-cruiser` boundary enforcement | Real value once you have 2+ contexts that must not import each other; overhead for one. | | `contracts/` layer + generated API client + `parse*` boundary | Only earns its keep once you're consuming a real backend's OpenAPI contract, not a public test API. | | Storybook + axe a11y gate | Testing/documentation infrastructure that pays off at a much bigger component count. | | CI pipeline | Nothing to gate yet with one context and no deploy target. | ## Growth path — when you outgrow this Each of these is a real, working pattern in the POC — copy it when you actually need it, not before: - **A page's state grows past 2-3 interacting fields, or needs undo/multi-step flow** → add an Elm-style store: `shared/application/store.ts` (`createStore`) + a `*.machine.ts` per feature (Model/Msg/pure `reduce`). - **You have 2+ contexts that must not import each other** → add `dependency-cruiser` (`.dependency-cruiser.js`) to enforce the `domain → application → infrastructure`/`ui` direction this template already follows by convention but doesn't check. - **You're consuming a real backend's OpenAPI contract** → add a `contracts/` layer (wire DTOs) + a generated typed client + a hand-written `parse*` boundary in `infrastructure/` (see ADR-0001, `.claude/skills/bff-endpoint/SKILL.md` if you're working from the POC directly). - **A second locale** → wrap user-facing copy in `$localize` with a stable custom id and add a translation `.xlf` file (see the POC's `CLAUDE.md` "User-facing copy" convention). - **A real design system** → vendor your CSS, then bridge your own token names onto it the way ADR-0003 (`docs/reference/architecture/0003-cibg-huisstijl.md` in the POC) does — keep your token names stable, only their values change. - **Testing/a11y at real component count** → add Storybook + the axe a11y addon so every component's states are visually verifiable and accessibility-checked, not just behavior-tested. ## Folder convention Each business capability is a context under `src/app//`, split into `domain/application/infrastructure/ui` — dependencies point inward (`ui → application → domain`; only `application` reaches `infrastructure`). Inside a context's `ui/`, components are organized by atomic-design layer (`atoms → molecules → organisms → templates → pages`); `shared/` holds only cross-context building blocks. When you add a second context, mirror `users/`'s shape.