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
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.