#for…of
The #for="… of …" construct allows for generating lists of elements easily within the template itself.
Note that this syntax applies only within the <template>
section of components.
Syntax
<element #for="item of iterable">…</element>
<template #for="item of iterable">…</template>
Parameters
- iterable
- An array or otherwise iterable object to iterate over.
- item
- The items in the iterable. This must not include an initializer, meaning there should be no var, let or const. The item variable is then available in the element's subtree as well as the other attributes on the element itself. This means all descendants also have access to the item variable, including
{{ inline }}
expressions.
- <element>
- Any element. It and its subtree are generated once for every item in the iterable. If this element is the <template> element, then only the children are generated for every item (not the <template> itself).
Note: Only #for="… of …" expressions are supported. While the for(initializer; condition; incrementer) form of for-loops is not supported, one may achieve the same goal by generating the array of iterated items before looping, and assigning it to a key in the component state object $
.
Details
For simple cases, #for="… of …" behaves just like one might expect; it iterates over the iterable, and generates elements for each item. Unlike some other frameworks or libraries, Yozo does not need a key attribute to keep track of which item is which. In classic Yozo fashion, this sacrifices some performance for simplicity and ease-of-use. However, Yozo does try to optimize things somewhat; for example, if the iterable triggers a re-render, items that did not change in value are not recomputed.
Examples
Generating list items
First, let's have a look at a basic example. We'll define a list of different drinks in our <script>
section, which we'll then render in a classic unordered list (<ul>). Note that the repeating element is the <li>, i.e. the list items themselves, so we'll need to put the #for attribute on the list items.
<title>drinks-list</title>
<template mode="closed">
<ul>
<li #for="drink of $.drinks">
{{ drink }}
</li>
</ul>
</template>
<script>
$.drinks = ['water', 'tea', 'coffee', 'soda'];
</script>
When the $.drinks array updates, then so will the list.
Objects vs primitives
In the previous example, our array contained primitives. In that case, iterating the array directly is fine. However, if our array contains objects, then iterating the items directly means we're losing the reactivity that they might have. To get around this, iterate over a live variable, instead of its plain value:
<title>drinks-list</title>
<template mode="closed">
<ul>
<li #for="$drink of $.$drinks">
{{ $drink.name }}
</li>
</ul>
</template>
<script>
$.drinks = [{ name: 'water' }, { name: 'tea' } /* , … */];
</script>
Just like when accessing deeper properties on live variables, we'll want to keep the entire accessing chain live until the very last property access. In the case of #for="… of …", that means iterating over live items whenever the items are objects.
Combining with #if
Sometimes, we might conditionally render a list of items, or have certain conditions on each item of a list. Unfortunately, attribute order is not guaranteed, and so writing #for="…" and #if="…" on the same element is ambiguous. Instead, split the attributes over two elements; one may be a <template> element, which, when used with a logical (#-prefixed) attribute, renders its children. For example, to render list items conditionally:
<title>healthy-drinks</title>
<template mode="closed">
<ul>
<template #for="drink of $.drinks">
<li #if="$.isHealthy(drink)">{{ drink }}</li>
</template>
</ul>
</template>
<script>
$.drinks = ['water', 'tea', 'coffee', 'soda'];
$.isHealthy = drink => { /* … */ };
</script>
Alternatively, to set a condition on whether or not to render a list might look something like
<title>drinks-and-fuels</title>
<template mode="closed">
<ul>
<template #if="$.showDrinks">
<li #for="drink of $.drinks">
{{ drink }}
</li>
</template>
<template #else>
<li #for="fuel of $.fuels" class="fuel">
{{ fuel }}
</li>
</template>
</template>
<script>
$.drinks = ['water', 'tea', 'coffee', 'soda'];
$.fuels = ['petrol', 'hydrogen'];
</script>
Note that while it is possible to use <template> wrappers for #for expressions regardless of whether or not they are necessary, it is more performance-friendly to avoid this and use #for on the elements themselves.
Usage notes
In situations where there's a need to render a lot of elements (e.g. several hundreds) or there's an otherwise performance-sensitive situation, it may be desirable to manage list rendering manually using an effect()
.
See also