Psst. It would be super cool if you could try the new Phoenix Boilerplate!

Try now

Tutorial

View on Github

Send events from JS to a LiveView component

This post was updated 01 May

csrfjavascriptliveviewphoenix

Let say you app uses a javascript library that needs to interact with your app. For example a LiveView component. That is possible with the built in Phoenix Channels and Phoenix PubSub.

STEP 1 - Generate a channel

Generata a new channel called App

mix phx.gen.channel App

In user_socket.ex add:

channel "app:*", TutorialWeb.AppChannel

Update the AppChannel component so it look like:

defmodule TutorialWeb.AppChannel do
  use TutorialWeb, :channel

  def join("app:" <> token, _payload, socket) do
    {:ok, assign(socket, :channel, "app:#{token}")}
  end

  # can be triggered by from the frontend js by:
  # channel.push('paginate', message)
  def handle_in("paginate", payload, socket) do
    Phoenix.PubSub.broadcast(Tutorial.PubSub, socket.assigns.channel, {"paginate", payload})
    {:noreply, socket}
  end

  def handle_info(_, socket), do: {:noreply, socket}
end

NOTE. I want every page request to have a unique channel name. And that channel name needs to be exposed to any LiveView component. So fot this purpose, I will use the csrf-token than is generated in the page header on every request.

In javascript, I can to this with

import {Socket} from "phoenix"

let socket = new Socket("/socket", {params: {token: window.userToken}})
socket.connect()

let channel = socket.channel(`app:${document.querySelector("meta[name='csrf-token']").getAttribute("content")}`, {})
channel.join()
  .receive("ok", resp => { console.log("Joined successfully", resp) })
  .receive("error", resp => { console.log("Unable to join", resp) })

Now, I have a channel name that looks something like:

app:FxBlXhI8Kj0WSQUrCi0vX0UIAhguFVZ9gZ5spuCEfqAg9Ou6radTlX01

So, If I send a channel event in JS to the channel, I have a handle_in-function that handles the event

  def handle_in("paginate", payload, socket) do
    Phoenix.PubSub.broadcast(Tutorial.PubSub, socket.assigns.channel, {"paginate", payload})
    {:noreply, socket}
  end

And in this function, I use Phoenix PubSub to broadcast the event to any subscribers.

STEP 2 - Subscribe to javascript events

Its easy to set up the a LiveView component to subscribe and handle events from Phoenix PubSub. In the last tutorial, I set up a LiveView component for handling pagination. I want to revisit that component and set up a subscription. Open lib/tutorial_web/live/product_list_live.ex and in the mount-function add:

if connected?(socket), do: Phoenix.PubSub.subscribe(Tutorial.PubSub, "app:#{csrf_token}")

So it looks like:

  def mount(%{"csrf_token" => csrf_token} = _session, socket) do
    if connected?(socket), do: Phoenix.PubSub.subscribe(Tutorial.PubSub, "app:#{csrf_token}")

    assigns = [
      conn: socket,
      csrf_token: csrf_token
    ]

    {:ok, assign(socket, assigns)}
  end

This means, when the component is connected, subscribe to anything that happens on the channel that corresponds to the curren csrf_token. This means, that if the channel will be unique per browser request.

The last piece here is to actually handle the messages we subscribe to.

Add in the same file:

  def handle_info({"paginate", %{"page" => page}}, socket) do
    {:noreply, live_redirect(socket, to: Routes.live_path(socket, ProductListLive, page: page))}
  end

  def handle_info(_, socket), do: {:noreply, socket}

STEP 3 - Send the message from JS

Now, that the backend code is wired up, I can add the final piece of the javascript. I will visit the page: http://localhost:4000/products and expect that I will send a pagination event after 5 seconds and navigate to page 3 http://localhost:4000/products?page=3

setTimeout(() => {
  channel.push('paginate', {page: '3'})
}, 3000)

Final Result

After a few moments the time out in setTimeout will kick in and the page will live_redirect to page 3


What are you working on?

If you want, you can send me a link to your Phoenix or Phoenix LiveView project so. Lets connect on Twitter or Linkedin.

- Andreas Eriksson, web developer since 2005

Related Tutorials

Published 03 Mar

Share LiveView state between tabs

javascriptliveviewphoenix

Each LiveView on each tab spawns a separate state. That might or might not be the desired behaviour. In this tutorial, I am going to share state bet..

Published 28 Jan

Phoenix LiveView and Invalid CSRF token

csrfformsliveviewphoenix

One issue that is common to run into is a CSRF error when posting some sort of form rendered with LiveView. The issue is that a LiveView component i..