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.

pages/index.azox
<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

with an index
<each item={todos()} as="todo" index="i">
  <li>{i + 1}. {todo}</li>
</each>

Objects

a list of 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 / else
<if cond={user()}>
  <p>Signed in as {user().name}</p>
<else />
  <a href="/login">Sign in</a>
</if>
if on its own
<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.

nested
<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.

a keyed list
<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.

rendered output
<!-- 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

AttributeOnMeaning
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