Repeat.for with index syntax

Are there any plans to extend repeat.for with a vue like syntax:

v-for="(item, index) in items"

In my opinion this is a very nice syntax, especially because it is close to the JS standard ([].forEach(item, idx) => {})

I know about “$index”, but when you have nested repeats it gets really ugly:

<div repeat.for="letter of letters">
    <div repeat.for="letter of letters">
         <span>${$index} / ${$parent.$index}</span>
    </div>
</div>

For now, I have an “indexed” value converter, which transforms an array to an indexed map, then I can use it like:

<div repeat.for="[idx, letter] of letters | indexed">
    <span>${idx} ${letter.x} / ${letters.length}</span>
</div>

// indexed-value-converter.ts

export class IndexedValueConverter
{
    toView(array: any[])
    {
        const map = new Map();
        array?.forEach((item, idx) =>
        {
            map.set(idx, item);
        });
        return map;
    }
}

What do you think about that?

4 Likes

if the directive name was forEach, then it would be really close:

<div v-foreach="(item, index) in items">

But in javascript, a normal for...of is like this:

for (var item of items)

So you can see where Aurelia is aligning to. Though maybe we can support both of them. Let’s ping @fkleuver and @EisenbergEffect

1 Like

Try “let”.

<div repeat.for="letter of letters">
    <let outer-index.bind="$index"></let>
    <div repeat.for="letter of letters">
         <span>${$index} / ${outerIndex}</span>
    </div>
</div>
4 Likes

@bigopon I think your “let” is way more flexible than a new but rigid syntax.

2 Likes

This and similar thoughts have been brought up a few times now, so clearly folks care about this more than just a bit.

However, I strongly believe that custom expression syntax is not the way to solve this. Let me explain why.

The ECMAScript language is described in perfect detail by a 800+ page specification, ~70 of which specifically describe the grammar and runtime behavior of expressions. This specification is written with great care by some of the greatest minds in the world, and it is what every browser and other JS runtime implementation relies upon.

So do we in our JIT expression parser that turns attribute values into runtime AST (and the nearly 20.000 tests verifying it) as well our runtime AST responsible for wiring up observation and evaluating those expressions (and the nearly 10.000 tests verifying it).

Deviations from the standard expression syntax introduce exceptions that our users need to learn as a framework-specific thing, and that most likely either clashes with existing valid syntax, or potential future syntax.

For example, this is already valid syntax (grouping operator): repeat.for="(item) of items" and it does the same thing as repeat.for="item of items", just like for ((item) of items) in normal JS. Does it make sense to do that? Not really. But that’s the grammar as per the specification.
So should we now make the grouping operator invalid in only for-of statements or in all expressions?
Only when there is a comma inside, or in all cases?
What about when it’s nested in another?
Could there be a situation where a user really needs the grouping operator that we might not be able to think of right now?
Who knows.

But we already have this issue with value converters and binding behaviors (which could be solved with the new pipe operator). Increasing the scope of this issue, in my opinion, is a mistake. It’s not a risk we want to take anymore, nor is thinking about all these expression parser rules with an ever-evolving specification.

So now what?

  • With destructuring, you can use the value converter approach.
  • With <let>, you can declare anything you want (just like you would have to in a normal loop)

We could also introduce a hook, for example:

<div repeat.for="item of items" callback="processItem">
</div>
processItem(context, item, items) {
  context.index = items.indexOf(item);
}

You could also share common logic this way. This could be an alternative to the hard-coded context properties added by the framework.

However with regards to nested repeaters, we could also make a few tweaks so that you don’t need to do the $parent.$index thing. That seems unnatural anyway.

In any case, I’m open for discussions w.r.t. a hook (or something like that) or tweaks to the semantics of context traversal, but custom syntax is a no-no for me and I hope folks would agree with that based on my earlier explanation.

I’ve not tried this, but would this not already work with v1?

export class WithIndexValueConverter {
  public toView(array: any[]) {
    // we should probably check `array == undefined` 🤷‍♂️
    return array.map((e, i) => [e, i]);
  }
}
<div repeat.for="[item, index] of items | withIndex">
  ${index} - ${item}
</div>
1 Like

Thank you all for your detailed feedback.

@timfish, it doesn’t work like that, but as described in the first post it works with a map:

toView(array: any[])
{
    const map = new Map();
    array?.forEach((item, idx) =>
    {
        map.set(idx, item);
    });
    return map;
}
1 Like