If you follow Elixir Twitter very closely, you probably noticed that Chris McCord released Phoenix LiveView on Friday. Chris (and the other original contributors) did a great job with the release: it has thorough documentation and I was especially impressed with the examples that were released at the same time. I’m going to walk you through creating a counter similar to one of the examples above, but I’m going to simplify it to be as basic as possible and try to explain each step of the way. Please reach out to me on Twitter if you have any questions about this.

New Project

To start out, let’s create a new Phoenix-based project called live_view_counter. We’ll use the Phoenix generator, and tell it not to include Ecto, since we won’t be using a database for this.

mix phx.new live_view_counter --no-ecto

Hit Y to install dependencies. Then cd into your project folder and add LiveView to the list of dependencies in the deps function of your mix.exs file:

{:phoenix_live_view, github: "phoenixframework/phoenix_live_view"}

Finally get the new dependency with mix deps.get.

Configure

Now we’ll want to add a signing salt. This is a security mechanism for guarding against man-in-the-middle attacks. You can use mix phx.gen.secret 32 to generate the salt for you, or just use the one I included below. Then put it into your config. Since this is just a test project, we’ll just put it straight in the config/config.exs file, but if we were doing this in a real project, we would want to put it in our secrets:

config :live_view_counter, LiveViewCounterWeb.Endpoint,
  live_view: [
    signing_salt: "7HekGYwxATz33gM/rH9q2mV+uKJq5/Hu"
  ]

The next bit of configuration is to tell Phoenix to use the LiveView template engine for rendering files with a .leex extension:

config :phoenix,
  template_engines: [leex: Phoenix.LiveView.Engine]

Now we want to add the LiveView flash plug to the browser pipeline in lib/live_view_counter_web/router.ex. We’re also going to add put_layout with LiveViewCounterWeb.LayoutView to wrap our live views in the default layout:

pipeline :browser do
  ...
  plug :fetch_flash
  plug Phoenix.LiveView.Flash
  ...
  plug :put_layout, {LiveViewCounterWeb.LayoutView, :app}
end

Then you’ll want to import some files in lib/live_view_counter_web.ex. This file is a large part of the “magic” behind Phoenix. It imports and aliases the functions that help you to craft controllers, views, routers, and channels. We’re going to modify it to import the LiveView functions that will be helpful to us:

def view do
  quote do
    ...
    import Phoenix.LiveView, only: [live_render: 2, live_render: 3]
  end
end

def router do
  quote do
    ...
    import Phoenix.LiveView.Router
  end
end

Next we’ll expose a websocket through which LiveView updates can be sent to the client in lib/live_view_counter_web/endpoint.ex:

defmodule LiveViewCounterWeb.Endpoint do
  use Phoenix.Endpoint

  socket "/live", Phoenix.LiveView.Socket
  ...
end

Then we’ll need to add LiveView Javascript dependencies in assets/package.json so that the client will talk to the LiveView backend:

{
  ...
  "dependencies": {
    "phoenix": "file:../deps/phoenix",
    "phoenix_html": "file:../deps/phoenix_html",
    "phoenix_live_view": "file:../deps/phoenix_live_view"
  }
  ...
}

Now install the new Javascript dependency with cd assets && npm install. We’re almost done configuring. Enable connecting to a LiveView socket at the bottom of assets/js/app.js:

import LiveSocket from "phoenix_live_view"

let liveSocket = new LiveSocket("/live")
liveSocket.connect()

Then let’s set modify our live reload patterns in config/dev.exs so that we get live reloading of LiveView templates:

config :live_view_counter, LiveViewCounterWeb.Endpoint,
  live_reload: [
    patterns: [
      ...,
      ~r{lib/live_view_counter_web/live/.*(ex)$}
    ]
  ]

And last step (this one is optional so you don’t need to), we’ll import the default CSS styles in assets/css/app.css by appending:

@import "../../deps/phoenix_live_view/assets/css/live_view.css";

And we’re all set up! To make sure everything is good you can run mix phx.server. Everything should compile and then if you go to http://localhost:4000 in your browser, you should see Replied phoenix:live_reload :ok. Once Phoenix LiveView is a little bit more stable, I imagine that it will be baked in to the Phoenix generator so that you don’t have to do all that boilerplate work every time. But now we’re on to the fun part.

The Counter

A counter that uses Phoenix LiveView to manage state in the backend

To create the counter, we’ll start by replacing our default route in lib/live_view_counter_web/router.ex with a new live route:

...
scope "/", LiveViewCounterWeb do
  pipe_through :browser

  live("/", CounterLive)
end
...

With the route set up, we’ll create a new folder in lib/live_view_counter_web called live with a counter_live.ex file inside of it. It appears that appending _live to the end of the file name will be the convention, similar to how controllers have _controller appended to the end. We’ll use Phoenix.LiveView to pull in all of the functions that we need to be able to create a LiveView view:

defmodule LiveViewCounterWeb.CounterLive do
  use Phoenix.LiveView
end

Now let’s add a render function to our module. This is the focal point of our new live module, as it renders the actual DOM element. We’ll render a counter that will display the current value as well as buttons for incrementing and decrementing the count:

def render(assigns) do
  ~L"""
  <div>
    <h1>The count is: <%= @val %></h1>
    <button phx-click="dec">-</button>
    <button phx-click="inc">+</button>
  </div>
  """
end

You’ll notice that our multiline string starts with a sigil you’re probably unfamiliar with: ~L. This is the new syntax for defining a LiveView template. Now when you run this, you’ll see an exception raised in the terminal:

** (ArgumentError) assign @val not available in eex template.

This is because we used @val in our render function, but it never gets assigned. We’ll take care of this with LiveView’s mount/2 function. Add it after the render function in counter_live.ex:

def mount(_session, socket) do
  {:ok, assign(socket, :val, 0)}
end

You’ll notice that uses the same syntax as assigning data to a Phoenix channel. mount/2 will be invoked when the live view is rendered from the controller. It receives the session data and LiveView socket, and should generate the necessary data for initially rendering the view. render/1 will be invoked once mount/2 is complete. So basically we’re now setting @val to 0 and then rendering the view. When you refresh your browser, the app should now display properly.

So now let’s look at those buttons we created. You’ll notice they have a phx-click attribute set on them. This creates a binding that sends a click event to the LiveView server. So let’s create functions to handle those events:

def handle_event("inc", _, socket) do
  {:noreply, update(socket, :val, &(&1 + 1))}
end

def handle_event("dec", _, socket) do
  {:noreply, update(socket, :val, &(&1 - 1))}
end

Try using the + and - buttons in your browser now, and then we’ll talk about how this works. But congratulations! You have an application with LiveView up and running. 🎉

The "inc" and "dec" values come from the value provided to the phx-click attribute. The second argument (that we’re ignoring in this case) is a value, but you could potentially use that to increment by different amounts rather than just 1 by adding a phx-value attribute to the buttons.

LiveEEx Templates

Now that we have a working app, let’s refactor it to use a LiveEEx template. Right now we have our code just sitting in the live view. Let’s start by creating a new file at lib/live_view_counter_web/templates/counter/index.html.leex. Then we’ll copy our HTML code from the render function into the new .leex file:

<div>
  <h1 phx-click="boom">The count is: <%= @val %></h1>
  <button phx-click="dec">-</button>
  <button phx-click="inc">+</button>
</div>

Then we’ll need to create a new view to render that file at lib/live_view_counter_web/views/counter_view.ex. It should be pretty simple:

defmodule LiveViewCounterWeb.CounterView do
  use LiveViewCounterWeb, :view
end

Now let’s modify our live view to use the new view and template:

defmodule LiveViewCounterWeb.CounterLive do
  use Phoenix.LiveView
  alias LiveViewCounterWeb.CounterView

  def render(assigns) do
    CounterView.render("index.html", assigns)
  end
  ...
end

So now we’re rendering out of a template and our code looks a bit cleaner! 🎉

Next Steps

Phoenix LiveView allows you to do some pretty cool stuff without ever having to touch frontend Javascript code. It’s still in the early stages, so you probably want to think quite a bit before using it in production. That being said, I really like the API of it. I’ll probably do a followup tutorial that tracks changes in a database. I’d recommend checking out the examples repo if you’re interested in seeing more of what you can do with LiveView.

Please let me know if you end up building anything cool with LiveView. I’m excited to see all the applications people dream up. Also please let me know if you have any feedback on this post or the video. You can find me on Twitter at dnsbty or just let me know in the comments of the video above. I plan to continue releasing videos like this, so if you’re interested, please like the video and subscribe to my channel so that YouTube will let you know when they come out. Thanks for reading!

Read this in Japanese

Thanks to Kikuchi Yutaka, this article is also available in Japanese. I’m grateful to him for taking the time to translate and share there. One of his readers also found an error with my post, and they let me know, and I’m very gradeful for that as well.