In this episode, we start building a new app using Phoenix LiveView—a library that makes it possible to write a full web app in Elixir, including the front-end. It's been gaining popularity since mid 2019 and is one of the best rapid prototyping tools I've ever worked with.
Overview
We'll start a LiveView app from scratch in this video. If you've already got Erlang, Elixir, Node and Phoenix installed, skip to the next section. If you still need to get them setup, then follow this ASDF tutorial.
The plan for this series is to make a new site for a podcast. It won't have the DB-side complexity of the CMS project, but it will include accounts, LiveView forms with validations, show notes, comments and interfacing with an external API.
Create a new Phoenix app and add LiveView (3:15)
Just like any other Phoenix app, we'll create it with mix:
mix phx.new reactor
Next, check your mix.exs
file and add phoenix_live_view
to your deps. I strongly recommend you use the same library versions I did while doing this series. Here are all the deps used at the time of this recording:
defp deps do
[
{:phoenix, "~> 1.4.9"},
{:phoenix_pubsub, "~> 1.1"},
{:phoenix_ecto, "~> 4.0"},
{:ecto_sql, "~> 3.1"},
{:postgrex, ">= 0.0.0"},
{:phoenix_html, "~> 2.11"},
{:phoenix_live_reload, "~> 1.2", only: :dev},
{:gettext, "~> 0.11"},
{:jason, "~> 1.0"},
{:plug_cowboy, "~> 2.0"},
{:phoenix_live_view, "~> 0.4.0"}
]
end
Configure the back-end to use LIveView
There are several small changes we need to make in order to use live view, mostly it's just a matter of including libraries:
router.ex
Add a plug for LiveView flash right after the fetch_flash
plug:
plug Phoenix.LiveView.Flash
web.ex
In this function, we'll be adding to the imports inside of the quote do
blocks. That way, modules with use ReactorWeb, :controller
(or :view
or :router
) macros will also also import LiveView functionality as well as typical Phoenix functionality.
In the controller imports, add:
import Phoenix.LiveView.Controller
In the view imports, add:
import Phoenix.LiveView,
only: [
live_render: 2,
live_render: 3,
live_link: 1,
live_link: 2,
live_component: 2,
live_component: 3,
live_component: 4
]
In the router imports, add:
import Phoenix.LiveView.Router
config.exs
In config.ex
, we need to add a line to our endpoint config for a LiveView signing salt:
config :reactor, ReactorWeb.Endpoint,
url: [host: "localhost"],
secret_key_base: "Aq2IH+MBzO3h1LgvK3EQn2Dmi2Cj2N55++zmEFahcmLJARgior2VAzzyFD7tHQS9",
render_errors: [view: ReactorWeb.ErrorView, accepts: ~w(html json)],
pubsub: [name: Reactor.PubSub, adapter: Phoenix.PubSub.PG2],
live_view: [signing_salt: "d0pWDhfsxTFfXBvWMjK14lAKelVTQKGS"] #new
The signing salt above is just an example. Generate your own from the terminal with mix phx.gen.secret 32
.
Configure the back-end to use LIveView
First add LiveView to your assets/package.json
file. Here is the full file:
{
"repository": {},
"license": "MIT",
"scripts": {
"deploy": "webpack --mode production",
"watch": "webpack --mode development --watch"
},
"dependencies": {
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html",
"phoenix_live_view": "file:../deps/phoenix_live_view"
},
"devDependencies": {
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-loader": "^8.0.0",
"copy-webpack-plugin": "^4.5.0",
"css-loader": "^2.1.1",
"mini-css-extract-plugin": "^0.4.0",
"optimize-css-assets-webpack-plugin": "^4.0.0",
"uglifyjs-webpack-plugin": "^1.2.4",
"webpack": "4.4.0",
"webpack-cli": "^2.0.10"
}
}
The only line added was `"phoenix_live_view": "file:../deps/phoenix_live_view"` and as you can see from the file path, this is actually pulling the JS part of LiveView from the Elixir library we pulled by adding `:phoenix_live_view` to our `mix.exs` at the beginning of this tutorial.
app.css
Import LiveView's CSS with:
@import "../../deps/phoenix_live_view/assets/css/live_view.css";
app.js
Add the following imports and setup after the import phoenix_html
line:
import { Socket } from "phoenix";
import { LiveSocket } from "phoenix_live_view";
let liveSocket = new LiveSocket("/live", Socket);
liveSocket.connect();
endpoint.ex
Now that we're creating an instance of a LiveSocket
in our main app.js
file and connecting to it, we need to add that socket to our endpoint.ex
(at the top right before the socket "/socket"
line):
socket "/live", Phoenix.LiveView.Socket
Try out the setup!
We'll just make a quick throw-away page on our app at /foo
that responds to keypresses. This can be done by creating a new route in router.ex
that dishes requests off to a new LiveView:
scope "/", ReactorWeb do
pipe_through :browser
get "/", PageController, :index
live "/foo", FooLive
end
There is no module called FooLive
yet, so we'll have to create one. For now, let's put it in a new directory, lib/reactor_web/live/
at the same level as our controllers and views and call the module foo_live.ex
:
defmodule ReactorWeb.FooLive do
use Phoenix.LiveView
def mount(_session, socket) do
{:ok, assign(socket, msg: "none")}
end
def render(assigns) do
~L"""
<h1>Hello!</h1>
<div phx-keydown="keydown" phx-target="window">
<%= @msg %>
</div>
"""
end
def handle_event("keydown", %{"key" => key}, socket) do
{:noreply, assign(socket, msg: key)}
end
end
At this point, you should be able to run the server and visit localhost:4000/foo
and see a greeting along with whatever key you've most recently pressed. Now let's go over a few things going on in the template.
The state for our view is held in the socket.assigns
, just like we could do with a normal Phoenix channel.
-
In the
mount
function, we have access to a session variable we aren't using as well as the socket. In this case we're just returning a tuple with:ok
and a the socket withsocket.assigns.msg
set to "none". -
Just like the
~E
sigil we can use in standard view render functions to embedeex
templates, we can use~L
to embed live templates within a live view's render function. -
Inside of this embedded template, we're accessing
socket.assigns.msg
with@msg
. It's exactly the same as if we'd writtenassigns.msg
. -
The
phx-keydown
attribute we set on the div listens for "keydown" events and passes them tohandle_event
. -
The
phx-target
attribute is setting where we're listening for the event (in this case, the entire window).
Done!
With this set up, there's definitely enough to play around with. Next episode, we'll throw away our FooLive module and start laying out the content site!
(Source code available for premium members)
4 Comments
Attention if you're upgrading LiveView to version 0.5.1 ahead of the tutorial series! You'll need to replace this in
web.ex
:with this:
For 0.6.0,
mount/2
ismount/3
and you'll need to add a new (unused) first argument of_
The series will cover upgrades. I strongly recommend using the same versions as the tutorials while working through them instead of using master. It will make it a lot easier.
I had to change
Glad to hear you got it working and thanks!
I ran into some issues w/ dependencies because I switched phx versions (from a newer version I had, back to the version you had in the tutorial)
Agreed, it's much easier to use the exact same versions of everything, because I ran into a number of problems w/ dependencies.