You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: apps/website/content/docs/render/guides/specs.mdx
+38-20Lines changed: 38 additions & 20 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,21 +4,21 @@ description: How the spec rendering example streams a JSON spec into a live comp
4
4
5
5
# Specs
6
6
7
-
A spec is the JSON object that describes an entire UI tree. It names a root element, holds every element in one flat map, and says for each element which component renders it, what props it receives, which children it owns, and when it is visible. The running example streams three specs in one character at a time and renders each one as it arrives, so the format on this page is the format you can watch being parsed.
7
+
A spec is the JSON object that describes an entire UI tree. It names a root element, holds every element in one flat map, and says for each element which component renders it, what props it receives, which children it owns, and when it is visible. The running example streams three specs one character at a time and renders each one as it arrives, so the format on this page is the format you can watch being parsed.
8
8
9
9
## What the demo does
10
10
11
11
The Run tab is split in two. The left side is the live render output, the right side is the raw JSON as it streams, and the bar along the bottom is a transport: play and pause, a track you can scrub to any character, a character count, and 1x, 2x and 4x speeds.
12
12
13
-
Three tabs across the top pick the spec. "Heading + Text" is a heading with one text child. "Card + Badge" is a card holding a badge and a paragraph. "Nested Layout" is a heading over two cards, each with its own text child. Picking a tab restarts the stream against that spec, and elements appear as soon as their props arrive, with skeleton placeholders standing in until they do.
13
+
Three tabs across the top pick the spec. "Heading + Text" is a heading with one text child. "Card + Badge" is a card holding a badge and a paragraph. "Nested Layout" is a heading over two cards, each with its own text child. Picking a tab restarts the stream against that spec, and elements appear as soon as their props arrive. The Text, Heading and Card components draw a skeleton placeholder while they wait; the Badge component has none, so a badge simply appears once its label arrives.
14
14
15
15
## How it is built
16
16
17
-
The example is one Angular file plus an application config. Open the Code tab to read them in place.
17
+
The Code tab holds the component and the application config. Open it to read them in place.
18
18
19
19
### The spec the demo streams
20
20
21
-
The three specs are plain JSON strings the component ships with. This is the first one, and it is the whole format in miniature: a `root` key, a flat `elements` map, and a `children` array that holds keys rather than nested objects.
21
+
The three specs live in `specs.ts`, beside the component, as plain JSON strings. This is the first one, and it is the whole format in miniature: a `root` key, a flat `elements` map, and a `children` array that holds keys rather than nested objects.
22
22
23
23
```json
24
24
{
@@ -31,7 +31,9 @@ The three specs are plain JSON strings the component ships with. This is the fir
31
31
},
32
32
"desc": {
33
33
"type": "Text",
34
-
"props": { "content": "This UI is rendered entirely from a JSON specification." }
34
+
"props": {
35
+
"content": "This UI is rendered entirely from a JSON specification. Each element maps to a registered Angular component."
36
+
}
35
37
}
36
38
}
37
39
}
@@ -71,7 +73,7 @@ The text component in the same file declares `childKeys` and never loops over it
71
73
72
74
### A partial spec still renders
73
75
74
-
The spec does not arrive whole. A partial JSON parser consumes the stream character by character and materializes whatever is complete so far, so the value handed to `<render-spec>` grows one element and one prop at a time.
76
+
The spec does not arrive whole. `StreamingSimulator`, a shared helper that sits beside the examples rather than in the block below, is the piece that feeds characters into an incremental JSON parser and materializes whatever is complete so far, so the value handed to `<render-spec>` grows one element and one prop at a time.
75
77
76
78
<ExampleCodefile="spec-rendering.component.ts"region="streaming-source"title="spec-rendering.component.ts — the streaming source" />
77
79
@@ -108,7 +110,7 @@ const spec: Spec = {
108
110
|----------|------|-------------|
109
111
|`root`|`string`| The key in `elements` that rendering starts from |
110
112
|`elements`|`Record<string, UIElement>`| A flat map of every element definition, keyed by a unique string |
111
-
|`state`|`Record<string, unknown>`| Optional. Seeds the internal state store that `<render-spec>` creates when no storeis supplied |
113
+
|`state`|`Record<string, unknown>`| Optional. Seeds the internal state store that `<render-spec>` creates when neither a `[store]` input nor a `provideRender({ store })`is supplied. The internal store is created once and is not re-seeded by a later spec|
112
114
113
115
<Callouttype="info"title="Flat element map">
114
116
Elements are stored in a flat map rather than a nested tree. Parent-child relationships are expressed through the `children` property, which holds keys pointing at other entries in the same map. That keeps lookups constant-time and makes a spec easy for a model to emit and to patch one element at a time.
|`props`|`Record<string, unknown>`| Inputs for the component. Static values or expressions |
137
141
|`children`|`string[]`| Keys of child elements in the same `elements` map |
138
142
|`visible`|`VisibilityCondition`| Visibility condition, evaluated against the store |
139
-
|`repeat`|`{ statePath: string; key?: string }`| Render this element once per item in a state array |
143
+
|`repeat`|`{ statePath: string; key?: string }`| Render this element once per item in a state array. `key` is schema-level and is not used by the Angular renderer|
140
144
|`on`|`Record<string, ActionBinding \| ActionBinding[]>`| Event name to the action or actions it fires |
141
-
|`watch`|`Record<string, ActionBinding \| ActionBinding[]>`| State path to the actions that fire when the value there changes |
145
+
|`watch`|`Record<string, ActionBinding \| ActionBinding[]>`| State path to the actions that fire when the value there changes. Part of the spec schema; the Angular renderer does not act on it today (use `on` bindings or an effect over the store)|
142
146
143
147
## Prop expressions
144
148
@@ -162,9 +166,9 @@ Arrays and plain objects are walked, so an expression nested inside a prop objec
162
166
`$bindState` resolves like `$state` and additionally populates the component's `bindings` input with the path, so the component can write back:
Pass the map through the `[functions]` input on `<render-spec>`, or register it once with [`provideRender()`](/docs/render/api/provide-render). An unregistered name resolves to `undefined` and logs a warning.
@@ -197,13 +201,25 @@ Pass the map through the `[functions]` input on `<render-spec>`, or register it
197
201
The `visible` property decides whether an element mounts. When it evaluates to false the element and everything under it stays out of the DOM. Omitting it means visible.
{ type: 'Text', props: { content: 'Shown when the flag is truthy' },
203
-
visible: { $state: '/showMessage' } }
212
+
const whenFlag:UIElement= {
213
+
type: 'Text',
214
+
props: { content: 'Shown when the flag is truthy' },
215
+
visible: { $state: '/showMessage' },
216
+
};
204
217
205
-
{ type: 'Text', props: { content: 'Shown when the count is over five' },
206
-
visible: { $state: '/count', gt: 5 } }
218
+
const whenOverFive:UIElement= {
219
+
type: 'Text',
220
+
props: { content: 'Shown when the count is over five' },
221
+
visible: { $state: '/count', gt: 5 },
222
+
};
207
223
```
208
224
209
225
A single condition reads `$state`, `$item` or `$index` and applies at most one comparison operator: `eq`, `neq`, `gt`, `gte`, `lt` or `lte`. With no operator it checks truthiness, and `not: true` inverts the result. An array of conditions is an implicit AND; `{ $and: [...] }` and `{ $or: [...] }` are the explicit forms and may nest.
@@ -213,14 +229,16 @@ A single condition reads `$state`, `$item` or `$index` and applies at most one c
213
229
`repeat` renders one copy of the element for each item in a state array:
214
230
215
231
```typescript
216
-
{
232
+
importtype { UIElement } from'@json-render/core';
233
+
234
+
const item:UIElement= {
217
235
type: 'ListItem',
218
236
props: {
219
237
label: { $item: 'name' },
220
238
position: { $index: true },
221
239
},
222
240
repeat: { statePath: '/todos' },
223
-
}
241
+
};
224
242
```
225
243
226
244
For each item the renderer builds a repeat scope holding the item, its index and its base path (`/todos/0`, `/todos/1`, and so on), provides that scope through a child injector, and resolves the element's props inside it, so `$item`, `$bindItem` and `$index` mean something different in every copy.
0 commit comments