docs: add architecture/dependency Mermaid diagrams and Elm-store guide
README now shows folder layering, minimal-Angular dependency flow, and the users feature data flow as diagrams. Adds docs/adding-a-store.md, a step-by-step guide to building an Elm-style store from just signals.
This commit is contained in:
@@ -19,6 +19,63 @@ npm start # ng serve
|
||||
npm test # ng test (Vitest)
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
Each business capability is a **context** (`users/`) split into
|
||||
`domain → application → infrastructure`/`ui` layers; dependencies only point inward.
|
||||
`shared/` holds cross-context building blocks, itself layered by atomic-design tier
|
||||
(`atoms → molecules → templates`). This is convention, not lint-enforced yet — see
|
||||
Growth path.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph shared["shared/ (cross-context)"]
|
||||
sapp["application/\nremote-data.ts"]
|
||||
sui["ui/\natoms → molecules → templates"]
|
||||
end
|
||||
|
||||
subgraph users["users/ (one context)"]
|
||||
udom["domain/\nuser.ts (no Angular import)"]
|
||||
uinfra["infrastructure/\nusers.adapter.ts (the only fetch() caller)"]
|
||||
uapp["application/\n*.resource.ts"]
|
||||
uui["ui/\norganisms + page"]
|
||||
end
|
||||
|
||||
uui --> uapp
|
||||
uapp --> udom
|
||||
uapp --> uinfra
|
||||
uui -. reuses .-> sui
|
||||
uapp -. reuses .-> sapp
|
||||
|
||||
classDef domain fill:#eef,stroke:#88a
|
||||
class udom,uinfra,uapp,uui domain
|
||||
```
|
||||
|
||||
### Why so few dependencies
|
||||
|
||||
The whole app runs on `@angular/core`'s signals + `resource()` and native `fetch` —
|
||||
nothing else is imported directly, even though a couple of these are installed
|
||||
transitively (by `@angular/forms`/`@angular/router`) or available and simply unused.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
app["Your app code"]
|
||||
|
||||
app -->|imports| core["@angular/core\nsignal · computed · resource"]
|
||||
app -->|calls| fetchApi["native fetch()"]
|
||||
|
||||
app -.->|"installed transitively,\nnever imported directly"| rxjs["rxjs"]
|
||||
app -.->|"installed,\nnever used — signal swap instead"| router["@angular/router"]
|
||||
app -.->|"never installed —\nzoneless by default"| zonejs["zone.js"]
|
||||
app -.->|"never installed"| ngrx["NgRx / any store lib"]
|
||||
app -.->|"never installed —\nfetch() instead"| http["HttpClient"]
|
||||
|
||||
classDef used fill:#dfe,stroke:#4a4
|
||||
classDef avoided fill:#fee,stroke:#a44,stroke-dasharray: 4 4
|
||||
class core,fetchApi used
|
||||
class rxjs,router,zonejs,ngrx,http avoided
|
||||
```
|
||||
|
||||
## What's here
|
||||
|
||||
- **`RemoteData<E, T>`** (`shared/application/remote-data.ts`) — a 4-variant union
|
||||
@@ -35,7 +92,27 @@ npm test # ng test (Vitest)
|
||||
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.
|
||||
`UserDetailComponent` that does its own independent fetch:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Page as UsersPage
|
||||
participant Res as usersResource()
|
||||
participant Api as fetchUsers (fetch)
|
||||
participant RD as fromResource()
|
||||
participant Async as app-async
|
||||
participant List as app-user-list
|
||||
|
||||
Page->>Res: usersResource()
|
||||
Res->>Api: loader()
|
||||
Api-->>Res: User[]
|
||||
Page->>RD: fromResource(usersResource, isEmptyUserList)
|
||||
RD-->>Async: RemoteData tag (Loading/Empty/Failure/Success)
|
||||
Async->>List: render on Success
|
||||
List->>Page: select.emit(id)
|
||||
Page->>Page: selectedUserId.set(id)
|
||||
Note over Page: template swaps to app-user-detail,<br/>which repeats the same chain via userDetailResource
|
||||
```
|
||||
- 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
|
||||
@@ -61,7 +138,9 @@ 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`).
|
||||
a `*.machine.ts` per feature (Model/Msg/pure `reduce`). See
|
||||
[`docs/adding-a-store.md`](docs/adding-a-store.md) for a step-by-step walkthrough
|
||||
that builds one from scratch using only signals — no new dependencies.
|
||||
- **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
|
||||
|
||||
Reference in New Issue
Block a user