4

I have a Alpine JS template that i want to reference x-data from an array. I currently have that declared at the bottom of my HTML file in a <script> tag, and i would like to move it in an external file. If I create the file and link it with <script src='path/to/file.js'> my component stops working.

Example that works:


<div x-data="{items}">
  <template x-for="(item, index) in items" :key="index">
    <a :href="item.link" x-text="item.text"></a>
  </template>
</div>
<script>
  const items = [
    { link: 'foo.com', text: 'foo' },
    { link: 'bar.com', text: 'bar' },
  ]
</script>

Example that doesn't work, where external.js has the same variable assignment

<div x-data="{items}">
  <template x-for="(item, index) in items" :key="index">
    <a :href="item.link" x-text="item.text"></a>
  </template>
</div>
<script src="external.js"></script>

And the contents of external.js file:

import 'alpinejs'

window.onload = () => {

console.log('loaded') // This logs in the console
  var items = [
    { link: 'foo.com', text: 'foo' },
    { link: 'bar.com', text: 'bar' },
  ]
}

I'm not sure what i'm doing wrong, help :(

Al3x4
  • 43
  • 1
  • 4

1 Answers1

5

Ok, so your problem is you're creating your items variable in a callback that listens to the window.onload event. AlpineJs will have already loaded and tried to parse the DOM including your variable before the event fires and your variable is defined.

We can see the load order by adding a console.log to the x-init attribute on an Alpine component:

<div x-data="{}" x-init="console.log('init')"></div>
<script>
window.onload = () => {
    console.log('loaded')
}
</script>

With this in place here's what we get in dev tools (working example https://jsfiddle.net/hfm7p2a6/):

The Solution

Fixing this depends a little on what you need to be in items. If there's no dynamic content being loaded (i.e. you're not needing to do an ajax call to get the content of your array) then simply forget the onload and place the variable in the top level of the script file (working example https://jsfiddle.net/nms7v4xh/):

// external.js
var items = ['your', 'items', 'array'];
// No window.onload needed

If you do need there to be dynamic content in there, you'll need to inject it into the AlpineJs instance. The best way to do this here is probably via a custom event (working example https://jsfiddle.net/6dLwqn4g/1/):

<div id="app" x-data="{items: null}" @my-event.window="items = $event.detail.items"></div>

<script>
// Create and dispatch the event
let event = new CustomEvent('my-event', {
    detail: {
        items: ['your', 'dynamic', 'items', 'content']
    }
});
window.dispatchEvent(event);
</script>

Here we're using AlpineJs to listen for an event on the window using the @my-event.window attribute (you could also use x-on:my-event.window). The items data is then available to us via the $event.detail property.

Hope that's helpful! Some extra reading below if you need.

https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent

Steve O
  • 5,224
  • 1
  • 24
  • 26