(Updated: )
/ #alpinejs #javascript 

Alpine.js `x-for` with objects: 4 ways to iterate/loop through JavaScript objects

One of the missing features of Alpine.js is the ability to iterate through objects with x-for.

Alpine.js is heavily inspired by Vue.js but it’s designed to be lean and rugged instead of incrementally adoptable. Therefore x-for only supports arrays/iterables.

The reason there’s no first-class support for iterating through objects with Alpine.js’ x-for is that converting a JavaScript Object to an Array is reasonably easy in modern JavaScript (ES6+) environments using Object.keys, Object.values or even Object.entries. This is the purpose of this post.

You can find all the examples at Alpine.js Playground - x-for with object.

Table of Contents

Concept: “normalised” data shape

In this example we’ll have some todos stored as on our instance, the data itself is of the following shape:

const todos = {
  1: {
    id: 1,
    text: 'todo-1'
  },
  2: {
    id: 2,
    text: 'todo-2'
  }
}

Note that this is often referred to as the “normalised” form of the data, because each item (todo) can be looked up by id eg. for id = 1: todos[1]. This is opposed to the array form in which lookups by id can be more difficult. For example we could store the same todos as:

const todos = [
  { id: 1, text: 'todo-1' },
  { id: 2, text: 'todo-2' }
]

In this case a lookup for the todo with id 1 will necessitate the use of the Array#find method ie. todos.find(el => el.id === 1), which is more verbose and complex.

We’ve now had a look at the data shape we’ll be using, we can put it into an inline Alpine.js component’s x-data:

<div
  x-data="{
    todos: {
      1: {
        id: 1,
        text: 'todo-1'
      },
      2: {
        id: 2,
        text: 'todo-2'
      }
    }
  }"
>
</div>

A basic feature of a todo app is the ability to display a list of todos. Next we’ll see how to display a list of normalised todos from state in Alpine.js using Object.keys.

Iterate through object keys/ids with x-for and Object.keys

Assuming a normalised data shape of todos keyed by id, if we need to list out the ids (1, 2), which are also the keys of the todos object we can use x-for="id in Object.keys(todos)" as per the following example.

<div
  x-data="{
    todos: {
      1: { id: 1, text: 'todo-1' },
      2: { id: 2, text: 'todo-2' }
    }
  }"
>
  <!-- rest of template -->
  <h3>Only need the keys/ids - use <code>Object.keys</code></h3>
  <ul>
    <template x-for="id in Object.keys(todos)" :key="id">
      <li>id: <span x-text="id"></span></li>
    </template>
  </ul>
</div>

Which yields the following output:

A list with id: 1 and id: 2

You can see it in action at Alpine.js Playground - x-for with object - Only need the keys/ids - use Object.keys.

We’ve now seen how to iterate through an object’s keys using x-for and Object.keys.

Next we’ll see how to iterate through object key-values pairs with x-for and Object.entries.

Iterate through object key-value pairs with x-for and Object.entries

When both the id/key and the value are required, we can iterate through a JavaScript object with Alpine.js’ x-for and Object.entries using the following directive: x-for="[id, value] in Object.entries(todos)".

A full example populating select options would look as follows:

<div
  x-data="{
    todos: {
      1: { id: 1, text: 'todo-1' },
      2: { id: 2, text: 'todo-2' }
    }
  }"
>
  <!-- rest of template -->
  <h3>Need keys/ids and values - use <code>Object.entries</code></h3>
  <select>
    <template
      x-for="[id, value] in Object.entries(todos)"
      :key="id"
    >
      <option :value="id" x-text="value.text"></option>
    </template>
  </select>
</div>

Note this is using Array destructuring, an alternative syntax for it that doesn’t required environment support for array destructuring is x-for="entry in Object.entries(todos)" and using entry[0] and entry[1] to refer to the id (key) and value respectively.

Alternative syntax (without array destructuring):

<div
  x-data="{
    todos: {
      1: { id: 1, text: 'todo-1' },
      2: { id: 2, text: 'todo-2' }
    }
  }"
>
  <!-- rest of template -->
  <h3>Need keys/ids and values - use <code>Object.entries</code></h3>
  <select>
    <template
      x-for="entry in Object.entries(todos)"
      :key="entry[0]"
    >
      <option :value="entry[0]" x-text="entry[1].text"></option>
    </template>
  </select>
</div>

Both of these are functionally equivalent albeit with a terseness/support tradeoff and yield the following output:

Select showing todo-1 and todo-2 options

You can see it in action at Alpine.js Playground - x-for with object - Need keys/ids and values - use Object.entries.

We’ve now seen how to use Object.entries and x-for to loop through an object when both the key (id) and vale is needed.

Next we’ll see an alternative way to achieve the same outcome using x-for, Object.values and object value lookup by id.

Iterate through object keys and look up corresponding object values with x-for and Object.keys

We’ve seen how to iterate through object keys using Object.keys. What’s great about a normalised data shape (entities keyed by id), is that looking up a value is just todos[id]. That means that we can achieve the same outcome as we did with Object.entries using only Object.keys.

We use x-for="id in Object.keys(todos) and then get the text using todos[id].text.

<div
  x-data="{
    todos: {
      1: { id: 1, text: 'todo-1' },
      2: { id: 2, text: 'todo-2' }
    }
  }"
>
  <h3>Need keys/ids and values - use <code>Object.keys</code> + lookup</h3>
  <select>
    <template x-for="id in Object.keys(todos)" :key="id">
      <option :value="id" x-text="todos[id].text"></option>
    </template>
  </select>
</div>

Which yields the following select with “todo-1” and “todo-2” options, where the value will be recorded as 1 and 2 respectively.

Select showing todo-1 and todo-2 options

You can see it in action at Alpine.js Playground - x-for with object - Need keys/ids and values - use Object.keys + lookup .

Nothing forces you to use the id once you have it, we can actually achieve something similar to our Object.values use case using Object.keys + a key lookup.

Here we x-for="id in Object.keys(todos)" and get the todo text using todos[id].text, not that the id isn’t displayed or used apart from to set the key.

<div
  x-data="{
    todos: {
      1: { id: 1, text: 'todo-1' },
      2: { id: 2, text: 'todo-2' }
    }
  }"
>
  <h3>Need values - use <code>Object.keys</code> + lookup</h3>
  <ul>
    <template x-for="id in Object.keys(todos)" :key="id">
      <li x-text="todos[id].text"></li>
    </template>
  </ul>

</div>

This outputs the following list, which looks pretty much like the list we’ll create using Object.values.

A list showing todo-1 and todo-2 values

You can see it in action at Alpine.js Playground - x-for with object: Need values - use Object.keys + lookup.

We’ve now seen how to iterate through an object’s keys using x-for and Object.keys as well as the flexibility a normalised data shape provides us. We showed this by re-implementing the other types of object iteration using Object.keys and key lookups.

Next we’ll look at iterating through object values using x-for and Object.values.

Iterate through object values with Object.values

For this guide to be complete, we’ll close off by looking at how to iterate through object values in Alpine.js using x-for and Object.values.

Object.values, as its name suggests, turns an object into an array of its values.

We can therefore use x-for="todo in Object.values(todos) and use the todo value in our loop template.

<div
  x-data="{
    todos: {
      1: { id: 1, text: 'todo-1' },
      2: { id: 2, text: 'todo-2' }
    }
  }"
>
  <h3>Need only values - use <code>Object.values</code></h3>
  <ul>
    <template x-for="todo in Object.values(todos)" :key="todo">
      <li x-text="todo.text"></li>
    </template>
  </ul>
</div>

This yields a similar list as we saw in the previous section, with todo-1 and todo-2.

A list showing todo-1 and todo-2 values

You can see it in action at Alpine.js Playground - x-for with object - Need only values - use Object.values.

Photo by Clay Banks on Unsplash

Author

Hugo Di Francesco

Co-author of "Professional JavaScript", "Front-End Development Projects with Vue.js" with Packt, "The Jest Handbook" (self-published). Hugo runs the Code with Hugo website helping over 100,000 developers every month and holds an MEng in Mathematical Computation from University College London (UCL). He has used JavaScript extensively to create scalable and performant platforms at companies such as Canon, Elsevier and (currently) Eurostar.

Interested in Alpine.js?

Subscribe to Alpine.js Weekly. A free, once–weekly email roundup of Alpine.js news and articles