Using LiveView with AlpineJS

Support for AlpineJS in LiveView was added in 0.13.3 and it works fabulously. I just wrote a blog article about it and plan another one soon on transitions with AlpineJS and Tailwind CSS.

25 Likes

Thanks for your really good read.
I’m always very skeptical in using any framework (:wink: it took me a long time until I’ve settled to used Phoenix).
Anyway Alpine.js could by a candidate to use.

But I’ve one question:
What’s the real benefit in using spruce for state management?
Isn’t the usage of localStorage not just enough or even better.
Sprouce seems to store the state in window - so it’s always just available in the current browser tab.
With localStorage I could manage to sync state across tabs, right?

I just can’t see the benefit in using, but please convince me of the opposite.

You can certainly use localStorage from Alpine, as this article demonstrates: https://codewithhugo.com/alpinejs-localstorage-sessionstorage/. So if you are skeptical about pulling in another framework, it is probably good enough.

For me, Spruce is nice because it integrates well with Alpine, allows you to watch for changes in state, and it works with x-model.

1 Like

Hmm, I need to prototype and check out x-model. Thanks.

I just published a follow-on article showing how to use AlpineJS and Tailwind CSS to create LiveView modals.

5 Likes

I was trying to integrate Apline and Phoenix LiveView and your blog posts have been of great help. Thank you for sharing your knowledge with us.

I have a question, what if we have multiple components on a page that want to register for Alpine Hook.

For ex

Hooks.Counter = {
  mounted() {
    window.counterHook = this
  },
  decrement() {
    this.pushEvent('decrement', {})
  },
  increment(selector) {
    this.pushEventTo(selector, 'increment', {})
  }
}

How do you create uniqueness above?

So i tried to do CustomEvent dispatch and get the push_element. This works -

<div x-data @push-element-updated="console.log('captured custom event')">
    <div id="test_tb_<%= @id %>" phx-hook="EscapeHook">
  </div>
Hooks.EscapeHook = {
  mounted() {
    console.log("mounted escape hook")
    let main_this = this;
    var event = new CustomEvent('push-element-updated', {
      bubbles: true,
      detail: {
        push_element: main_this
      }
    });
    console.log("event dispatched =======")
    this.el.dispatchEvent(event);
  }
}

But the above doesn’t work when the EscapeHook element is put inside a conditional. For ex -

<%= if @is_being_edited do %>
<div x-data @push-element-updated="console.log('captured custom event')">
    <div id="test_tb_<%= @id %>" phx-hook="EscapeHook">
  </div>
<% end %>

How can we make it work inside a conditional?

Thank you once again for taking the time to enlighten us all.

Nothing is obviously wrong to me. I frequently use Alpine in conditionally rendered markup. I have an example of that in my article on modals. See the alternative modal implementation section.

Try adding an id on the container div inside the conditional. See if that makes any change.

I’m really excited to try this out. I just tried adding AlpineJS to a LiveView project (following this post) and the browser console is now reporting this issue:

alpine.js:2252 Uncaught ReferenceError: regeneratorRuntime is not defined
    at eval (alpine.js:2252)
    at eval (alpine.js:2293)
    at eval (alpine.js:34)
    at eval (alpine.js:39)
    at Object../node_modules/alpinejs/dist/alpine.js (app.js:142)
    at __webpack_require__ (app.js:20)
    at eval (app.js:2)
    at Module../js/app.js (app.js:131)
    at __webpack_require__ (app.js:20)
    at app.js:84

And I couldn’t google out anything that would apply (I can see lots of issues related to babel).

I npm install-ed AlpineJS 2.8.2.

so you did this?

assets$ git diff HEAD js/app.js
 import "../css/app.css"
+import Alpine from "alpinejs";
 ...
-let liveSocket = new LiveSocket("/live", Socket, { params: { _csrf_token: csrfToken } })
+let liveSocket = new LiveSocket("/live", Socket, {
+   params: { _csrf_token: csrfToken },
+   dom: {
+   	onBeforeElUpdated(from, to) {
+       	if (from.__x) {
+           	Alpine.clone(from.__x, to);
+            }
+       },
+   },
+}
+)

When I did this I had trouble with latest node, so I downgraded to node 14.
With latest phoenix (not really, 1.5.7 actually), node 14 and AlpineJS 2.8.1 (2.82 should not make a difference) it just works (under ubuntu 20.4). All I got to do is

assets$ npm install alpinejs

And the patch above.
Obviously you have to start from a working phx.new

Yes, all of the above is correct. Except I’m on node v10.24.1 and Elixir 1.5.8.

I finally managed to find a solution on the Hotwire discuss.

I prepended this require to app.js:

import "regenerator-runtime/runtime"

And now it’s working.

However I would still be interested in an explanation why this was needed for me (and doesn’t seem needed for most people?). Maybe this is something that needs to be mentioned in the AlpineJS readme?

The npm moves in mysterious ways.

1 Like

Hi, this has been asked a while ago, but, even though I followed your guide, my live view pages still have no interactivity through Alpine. I mean, Alpine is not even being loaded when I look at the window object.

Do you know if the newer phoenix versions have problems with Alpine ?

If you are using the latest version of Alpine.js (v3), you will need to make some changes in how you set it up.

Alessandro Di Maria has a short guide on the changes you need to make in this article: Update LiveView for Alpine.js 101 - DEV Community.

4 Likes

Is there any documentation (besides the sources) that explains this? I used Alpine with liveview some times now and I’d like to understand the magic that’s going on here.

I see…
Yeah, I was inserting the plugin the old way. That should do it

Thank you!

Hi, I came back to say that I did the inclusion of Alpine in a slightly different manner. I’m using Alpine 3.2.1 and the snipped showed in the post helped, but didn’t solved all the problems I was having. So I’m leaving my snippet for anyone who comes across the same problem and can’t solve using the post:

Edit: I also should mention that maybe I have something wrong in my code that forces me to do things this way. I’m a beginner at Elixir/Phoenix, so if something looks off about this code or the approach, please, correct me :slight_smile:

// FIRST IMPORT THE ALPINE MODULE AS YOU ARE USED TO
import Alpine from 'alpinejs';
// I'VE ALSO ATTACHED IT TO THE WINDOW OBJECT
window.Alpine = Alpine;

let liveSocket = new LiveSocket("/live", Socket, {
    dom: {
        onBeforeElUpdated(from, to) {
            if (from.__x) {
                window.Alpine.clone(from.__x, to);
            }
            
            if (from._x_dataStack) {
                window.Alpine.clone(from, to);
                // I HAD TO INITIALIZE THE TREE EVERYTIME IT UPDATES. 
                // I WAS FACING "Alpine is not defined" ERROR WHEN I DIDN'T
                window.Alpine.initTree(to)
            }
        }
    },
    params: { _csrf_token: csrfToken },
})
...
// DON'T FORGET TO START ALPINE AND DON'T PUT THIS SNIPPET
// INSIDE THE onBeforeElUpdate HOOK. 
// I KNOW THAT SEEMS IRRELEVANT, BUT IT COULD CAUSE A MASSIVE MEMORY LEAK
// BECAUSE IT WILL DUPLICATE ALL LISTENERS EVERYTIME THE VIEW UPDATES
Alpine.start()

Thank you for the help!

1 Like