Loops & conditionals
<each> renders a list and <if> picks a branch. Both are tags rather than a separate template language, so they nest inside your markup like anything else.
Rendering a list
item is the list, and as names the variable the body uses for each entry — the way a parameter names an argument.
<script>
import { signal } from 'azox/reactivity';
const todos = signal(['Write docs', 'Ship it']);
</script>
<ul>
<each item={todos()} as="todo">
<li>{todo}</li>
</each>
</ul>The index
<each item={todos()} as="todo" index="i">
<li>{i + 1}. {todo}</li>
</each>Objects
<script>
import { signal } from 'azox/reactivity';
const people = signal([
{ name: 'Ada', role: 'Engineer' },
{ name: 'Grace', role: 'Admiral' },
]);
</script>
<ul>
<each item={people()} as="person">
<li>
<strong>{person.name}</strong> — {person.role}
</li>
</each>
</ul>An empty list renders nothing, and so does a list that is null or undefined — useful while data is still loading.
Branching
cond takes any expression. <else /> marks where the second branch begins.
<if cond={user()}>
<p>Signed in as {user().name}</p>
<else />
<a href="/login">Sign in</a>
</if><if cond={errors().length > 0}>
<p class="error">Something went wrong.</p>
</if>Nesting
A conditional inside a loop sees the loop variable, and loops nest as deeply as you need.
<each item={tasks()} as="task">
<li>
<if cond={task.done}>
<s>{task.title}</s>
<else />
<span>{task.title}</span>
</if>
</li>
</each>How updates work
Each block marks its place in the DOM with a pair of comment nodes. When its source signal changes, the nodes between those markers are replaced — and nothing outside them is touched.
The body is compiled once into a small builder function that is called per item, rather than being duplicated per entry. Everything else on the page, including other lists, is unaffected by an update here.
Keeping rows across a change
By default a change to the list rebuilds its contents. That is fine for display, but anything a row was holding — a count, focus, a scroll position — starts again.
Give each row an identity with key, and rows survive: reordering moves them, removing one leaves the rest untouched, and adding one does not disturb what is already there.
<each item={tasks()} as="task" key={task.id}>
<li>
<TaskRow title={task.title} />
</li>
</each>The key is an expression evaluated per row, so it can read the loop variable. Use something stable and unique to the row — a database id, not its position, since a position changes when the list does.
A row that leaves has its effects released, and any onCleanup it registered runs then. Rows that stay are left alone — they keep their state and go on updating.
Keys must be unique within a list. A duplicate is reported rather than quietly dropping a row, since the two rows would otherwise be indistinguishable.
Without a key, a list still rebuilds. That is the right default for a list of plain text, and it costs nothing to leave as it is. Reach for a key when a row holds something worth keeping.
A keyed row's index does not renumber. The row is built once, so its index is the position it was built at. Reordering moves the rows without changing their numbers — that is the cost of keeping a row instead of rebuilding it. When a list reorders and the position matters, read it from your own data.
Server rendering
Both are evaluated during the build, so a page arrives with its list already in the HTML rather than filling in once scripts run.
<!-- pages/index.azox -->
<ul>
<each item={todos()} as="todo">
<li>{todo}</li>
</each>
</ul>
<!-- what the server sends -->
<ul><li>Write docs</li><li>Ship it</li></ul>Reference
| Attribute | On | Meaning |
|---|---|---|
item={…} | <each> | The list to iterate. Required. |
as="name" | <each> | Names the current entry. Required. |
index="name" | <each> | Names the position, starting at 0. Optional. |
key={…} | <each> | Gives a row an identity so it survives a change. Optional. |
cond={…} | <if> | The expression to test. Required. |
Next
- Components — reusing markup
- Limitations — what is still missing