*each
Summary
*each repeats the children of a single host element for each entry in a list, object, or other iterable. The host element itself is rendered exactly once and acts as a container. The directive understands JavaScript-like in and of loop syntax and has an alias n-each.
Use *each when you want one structural wrapper (such as <ul>, <tbody>, or <div>) whose contents are repeated.
Important restriction:
- A single element must not combine
*eachwith*includeor*import.
These structural directives all want to control the host’s children, so they are not allowed on the same element.
Basic example
A simple list:
<serc-rod id="app" data='{"items":["Apple","Banana","Cherry"]}'>
<ul *each="item of items">
<li *print="item"></li>
</ul>
</serc-rod>
Behavior:
<ul>is rendered once.*each="item of items"iterates the array.- For each item, Sercrod renders the original
<li>subtree with a local variableitembound to the current value. - The result is a single
<ul>containing three<li>elements.
Behavior
*eachis a structural directive that controls how many times the original child nodes are rendered.- The host element is cloned once as a container; its original children are used as a template for each iteration.
- The expression on
*eachis evaluated once per render of the host. - Inside each iteration, Sercrod renders the original children with an iteration-specific scope.
Alias:
*eachandn-eachare aliases. They accept the same syntax and behave identically.
Expression syntax
The expression to the right of *each uses a restricted, JS-like loop syntax:
value of iterable(key, value) of iterablekey in object
key and value must be simple identifiers (no destructuring or complex patterns).
Supported and recommended patterns:
-
Array values:
-
item of items
Iterates an array, bindingitemto each element. -
(index, item) of items
Iterates an array, bindingindexto the numeric index anditemto the element.
-
-
Object values:
-
key in obj
Iterates over the enumerable keys ofobj, bindingkeyto each property name. -
(key, value) of obj
Iterates overObject.entries(obj), bindingkeyto the property name andvalueto the value.
-
Collection evaluation:
- The right-hand side is evaluated as a normal Sercrod expression.
- If the expression returns a falsy value (such as
null,undefined,false,0, or an empty string),*eachtreats it as an empty collection and renders nothing. - Arrays and plain objects are the primary targets; other iterables may work but are not the main focus.
Value semantics
For modeWord = "of":
-
item of items:- If
itemsis an array, each iteration seesitemas one element of the array. - If
itemsis a non-null, non-array object,*eachiteratesObject.values(items), soitemis each value.
- If
-
(index, item) of items:- If
itemsis an array, Sercrod usesArray.from(items.entries()).indexreceives the numeric index.itemreceives the element.
- If
itemsis a non-null object, Sercrod usesObject.entries(items).indexreceives the key.itemreceives the value.
- If
For modeWord = "in":
-
key in obj:- Sercrod runs a
for...instyle enumeration. - The single variable (
keyin this example) receives each property name. - To access values, you write expressions like
obj[key]inside the body.
- Sercrod runs a
-
(key, value) in obj:- The current implementation of
*eachdoes not bindkeyandvalueseparately. - Only the second name is bound, and it receives the key string.
- This form is therefore not usable in practice for
*each. - If you need both key and value, always use
(key, value) of expr.
- The current implementation of
Recommendation:
- Use
ofwhen you need values, or key and value pairs. - Use
inonly when you need keys, and stick to the single-variable form (key in obj) with*each.
Evaluation timing
*each participates in Sercrod’s structural evaluation order:
- Host-level
*ifandn-ifrun before*each.- If a host
*ifcondition is falsy, the host and its children are dropped and*eachdoes not run.
- If a host
*switchon the same host (if present) is processed before*eachand may completely replace the children.- After those structural checks,
*eachis evaluated on the host. - The expression for
*eachis evaluated once per render of the host, not once per iteration. - Child directives (
*ifon child elements, nested*for, nested*each,*include, bindings, event handlers, and so on) are evaluated separately for each iteration when the child nodes are rendered.
Execution model
Conceptually, the runtime behaves like this when it encounters *each:
- Evaluate the expression on
*eachto obtain a collection (iterable).- If the result is falsy, treat it as an empty collection and stop.
- Create a shallow clone of the host element as a container.
- The tag name and all attributes are copied.
- The
*each/n-eachattribute is removed from the clone.
- Take the original child nodes of the host as the template body.
- For each entry in the collection:
- Prepare a per-iteration scope that merges:
- The effective parent scope.
- The per-iteration variables (for example
item,index,key,value).
- Render each original child node with this scope into the container.
- Prepare a per-iteration scope that merges:
- Append the container to the parent of the original host.
- The original host node is not appended.
On re-renders:
- When the surrounding Sercrod host re-renders,
*eachre-evaluates the expression and rebuilds the loop body from the original template children. - There is no diffing or keyed patching; the children are regenerated from the template, which keeps the implementation small and predictable.
Variable creation and scope layering
Inside the body of *each:
- The loop variables (
item,index,key,value, or whatever names you choose) are added to the scope for each iteration. - These variables shadow any outer variables with the same names.
- All existing scope entries remain available:
- Data from the host (
dataor whatever you bound on<serc-rod>). - Special helpers like
$data,$root, and$parentinjected by Sercrod. - Methods injected via
*methodsor similar configuration.
- Data from the host (
Guidelines:
- Choose iteration variable names that do not unintentionally shadow important outer variables.
- If you need to access the original collection while using a short name for the item, keep a long-form name in the data, such as
state.items, and refer tostate.itemswhen needed.
Parent access
*each does not introduce a separate parent object, but parent data remain available through the normal Sercrod scope model:
- You can access outer data through whatever names you used in
data(for exampleitems,state,config). - You can access the root data with
$root. - You can access the nearest ancestor Sercrod host’s data with
$parent.
The only additional names introduced by *each are the loop variables themselves.
Use with conditionals and loops
*each is designed to compose with other directives when they target different layers:
-
Host-level condition:
*ifon the same element is evaluated first and acts as a gate for the entire loop.
<ul *if="items && items.length" *each="item of items"> <li *print="item.label"></li> </ul>- If
itemsis falsy or has zero length, the<ul>and its body are not rendered at all.
-
Child-level conditions:
- You can freely use
*ifor*forinside the body of*each.
<ul *each="item of items"> <li *if="item.visible"> <span *print="item.label"></span> </li> </ul> - You can freely use
-
Nested loops:
- Nested
*foror nested*eachinside the body of*eachare allowed. - Each nested loop sees the outer loop’s variables in its scope.
<table> <tbody *each="row of rows"> <tr *each="cell of row.cells"> <td *print="cell.text"></td> </tr> </tbody> </table>- In this example,
<tbody>is rendered once, and its<tr>children are repeated per row, with<td>repeated per cell.
- Nested
Use with templates, *include and *import
*each, *include, and *import are all structural directives that control the children of a host element, but in different ways:
-
*each:- Takes the original children of the host as a template.
- Repeats those children for each entry in a collection.
-
*include:- Finds a named
*template. - Replaces the host’s inner content with the inner content of that template.
- Finds a named
-
*import:- Is typically a higher-level helper that internally relies on the template/include mechanism.
- Also wants to control the host’s children as a single unit.
Because all of these directives want to own the host’s children, putting them on the same element is not supported.
Invalid patterns:
<ul *each="item of items" *include="'user-item'">
<!-- This combination is not supported -->
</ul>
<ul *each="item of items" *import="'user-item'">
<!-- This combination is also not supported -->
</ul>
Reasons:
*eachexpects to read the host’s original children and use them as the loop body.*includeand*importwant to overwrite the host’s children with template content.- The implementation does not merge these behaviors; whichever transformation happens first would effectively erase the assumptions of the others.
- The result is undefined and will almost certainly break expectations.
Supported patterns:
-
Put
*eachon the container and*includeor*importon a child element:<ul *each="item of items"> <li *include="'user-item'"></li> </ul> <ul *each="item of items"> <li *import="'user-item'"></li> </ul>In these cases:
<ul>is the loop container, rendered once.<li>is the template body for each iteration.*include/*importruns independently inside each iteration, and the included template can useitem.
-
Or use
*include/*importto bring in a template that contains its own loop:<section *include="'user-list-block'"></section> <section *import="'user-list-block'"></section>Where
user-list-blockis a*templatethat uses*eachinternally.
If your project wraps *include with other helpers, or defines *import as such a helper, the same restriction applies: do not put those helpers on the same element as *each.
Comparison with *for
Both *for and *each iterate collections, but they do so at different structural levels.
-
*for:- Repeats the host element itself.
- Typical pattern for repeated siblings.
<ul> <li *for="item of items"> <span *print="item"></span> </li> </ul> -
*each:- Keeps the host element as a single container and repeats its children.
<ul *each="item of items"> <li *print="item"></li> </ul>
Recommendations:
- Use
*forwhen the host itself is the repeated unit (list items, cards, rows). - Use
*eachwhen the host is a structural wrapper that must stay unique (a<ul>,<tbody>,<g>in SVG, and similar).
Best practices
-
Prefer
offor value iteration:- Use
item of itemsor(index, item) of itemsfor arrays. - Use
(key, value) of objwhen you need both key and value.
- Use
-
Use
inonly for keys:key in objis appropriate when you only care about property names.- Avoid
(key, value) in exprwith*each; the current implementation does not bind the pair as you might expect.
-
Keep expressions simple:
- If you need complex filtering or sorting, consider precomputing the collection in data or in a method instead of writing a very long expression in
*each.
- If you need complex filtering or sorting, consider precomputing the collection in data or in a method instead of writing a very long expression in
-
Avoid mutating the iterated collection in the body:
- Modifying the array or object you are looping over while rendering makes behavior harder to reason about.
- Prefer to build a derived collection and iterate that.
-
Use containers that match markup semantics:
- For table rows, use
*eachon<tbody>or<thead>and keep<tr>as the repeated child. - For lists, use
*eachon<ul>or<ol>only when you explicitly want a single list node.
- For table rows, use
-
Remember the structural restriction:
- Do not combine
*eachwith*includeor*importon the same element.
- Do not combine
Additional examples
Iterating over an object map:
<serc-rod id="app" data='{
"users": {
"u1": { "name": "Alice" },
"u2": { "name": "Bob" }
}
}'>
<dl *each="(id, user) of users">
<dt *print="id"></dt>
<dd *print="user.name"></dd>
</dl>
</serc-rod>
Using *each on <tbody>:
<serc-rod id="table" data='{
"rows": [
{ "id": 1, "name": "Alpha" },
{ "id": 2, "name": "Beta" }
]
}'>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
</tr>
</thead>
<tbody *each="row of rows">
<tr>
<td *print="row.id"></td>
<td *print="row.name"></td>
</tr>
</tbody>
</table>
</serc-rod>
Notes
*eachandn-eachare aliases; choose one style per project for consistency.- The expression on
*eachis evaluated as normal JavaScript inside Sercrod’s expression sandbox. - In the current implementation:
*eachexpects arrays, plain objects, or other iterable values.- Falsy results behave like an empty collection.
(key, value) in expris parsed but not useful; always prefer(key, value) of exprwhen using*each.
- Structural combinations where multiple directives compete for the same host’s children (such as
*eachplus*includeor*eachplus*importon one element) are not supported and should be avoided.