Updated for LiveView 0.11.0 – 7th April 2020
Understanding Phoenix LiveView
- Setup (this)
- The Primitives
- Build a Gallery app
Phoenix LiveView has been released on Hex! Although it’s still not a stable release (at the time of writing 0.11.0), it works really well and it’s packed of cool features.
Since the first version, I’m so impressed with how easy and fast I can develop a real-time application with LiveView, without using a complex JavaScript framework.
But to me, the most important LiveView’s aspect is the productivity boost! Be able to focus on where data reside and are processed while being sure that changes are pushed to the front-end, updating the UI automatically. It really feels like magic!
In this article we see how to get started with Phoenix LiveView by creating a new Phoenix app and going through the LiveView setup.
In the next articles we’ll see how to use this setup to build a simple LiveView application called Gallery, which we’ll use to develop our first LiveView page and understand how LiveView works under the hood.
In this app the user can interact with the LiveView page and go through a gallery of images by clicking the interface buttons.
Create a new Phoenix project
Let’s first create a new Phoenix project. We need the latest stable Phoenix Framework and Node.js installed.
$ mix phx.new gallery --no-ecto
Since we don’t need any database, we use the --no-ecto
option. Our gallery will just be a list of image urls.
Now, how can we setup LiveView? At the moment there are no LiveView generators, so we need to manually integrate it, changing some files and macros.
:phoenix_live_view
in mix.exs
Let’s start by adding the phoenix_live_view library under the mix.exs
dependencies. We can set the release version we find on Hex
defp deps do
[
{:phoenix, "~> 1.4.16"},
...
{:phoenix_live_view, "~> 0.11.0"}
]
end
or if you prefer to try the latest committed code, you can directly use the GitHub LiveView master branch
defp deps do
[
{:phoenix, "~> 1.4.16"},
...
{:phoenix_live_view, github: "phoenixframework/phoenix_live_view"}
]
end
In our case, we set the 0.11.0 version and get the dependencies running mix deps.get
in the terminal
$ mix deps.get
...
New:
phoenix_live_view 0.6.0
* Getting phoenix_live_view (Hex package)
Signing Salt in config/config.exs
Now we need to update the endpoint configuration in config/config.exs
, adding the LiveView signing salt.
To generate a new salt we use the mix task
$ mix phx.gen.secret 32
LIlgBfJ9j7xLJ6Almy982/ZydK/9y0vd
# config/config.exs
config :gallery, GalleryWeb.Endpoint,
...
live_view: [
signing_salt: "LIlgBfJ9j7xLJ6Almy982/ZydK/9y0vd"
]
LiveView Flash plug
Then, we go in lib/gallery_web/router.ex
and, in our :browser
pipeline, we exchange the plug :fetch_flash
with the LiveView plug :fetch_live_flash
line.
# lib/gallery_web/router.ex
pipeline :browser do
...
plug :fetch_session
# plug :fetch_flash
plug :fetch_live_flash
plug :protect_from_forgery
...
end
Imports in lib/gallery_web.ex
We now update the lib/gallery_web.ex
web file, adding some import
s to the controller
, view
and router
functions
# lib/gallery_web.ex
def controller do
quote do
...
import Phoenix.LiveView.Controller
end
end
def view do
quote do
...
import Phoenix.LiveView.Helpers
end
end
def router do
quote do
...
import Phoenix.LiveView.Router
end
end
Socket in lib/gallery_web/endpoint.ex
Next, we expose a new socket for LiveView updates in the endpoint module lib/gallery_web/endpoint.ex
. This socket is used by LiveView to send updates and receive events.
defmodule GalleryWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :gallery
@session_options [
store: :cookie,
key: "_gallery_key",
signing_salt: "rWCMKEW0"
]
socket "/live", Phoenix.LiveView.Socket,
websocket: [connect_info: [session: @session_options]]
...
end
JavaScript and CSS
Now let’s focus on the client side. At first let’s check we have the CSRF meta tag inside the <head>
tag, in the layout lib/gallery_web/templates/layout/app.html.eex
<!DOCTYPE html>
<html lang="en">
<head>
<%= csrf_meta_tag() %>
...
</head>
...
</html>
then we add the LiveView JavaScript library in assets/package.json
.
{
"dependencies": {
...
"phoenix_live_view": "file:../deps/phoenix_live_view"
}
}
This library will run on the browser, updating the DOM, managing events while talking with the LiveView process on the server.
To install the new javascript dependency we’ve just added, run on the terminal
$ npm install --prefix assets
We are almost ready, we just need to add five lines of javascript code in assets/js/app.js
, which starts LiveView on the client with the csrfToken
taken from the CSRF meta tag
// assets/js/app.js
import {Socket} from "phoenix"
import LiveSocket from "phoenix_live_view"
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
liveSocket.connect()
and import the default LiveView CSS in assets/css/app.css
/* assets/css/app.css */
@import "../../deps/phoenix_live_view/assets/css/live_view.css";
Done! Let’s see if everything works
Great, the setup is finished and LiveView is ready! Let’s see now if everything works by creating a super-simple LiveView module that renders some text.
After creating the lib/gallery_web/live
directory, we open in it a new file called gallery_live.ex
. In this new file we define the GalleryWeb.GalleryLive
module (our LiveView page – just copy/paste the code below. We will dig into LiveView functionalities in next articles)
defmodule GalleryWeb.GalleryLive do
use Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok, socket}
end
def render(assigns) do
~L"""
<h1>LiveView is awesome!</h1>
"""
end
end
Inside the render/1
function we have a LiveView template that will show the message “LiveView is awesome!” on our browser.
Almost there… we just need to add a live
route in our router lib/gallery_web/router.ex
# lib/gallery_web/router.ex
defmodule GalleryWeb.Router do
use GalleryWeb, :router
...
scope "/", GalleryWeb do
...
live "/gallery", GalleryLive
end
end
and after running the Phoenix server
$ mix phx.server
[info] Running GalleryWeb.Endpoint with cowboy 2.7.0 at 0.0.0.0:4000 (http)
[info] Access GalleryWeb.Endpoint at http://localhost:4000
...
opening the page http://localhost:4000/gallery we should see our LiveView page
It works, but the result is different from what we may expect: the view is without the default layout.
Live Layouts
When working with LiveViews there are three layouts we need to consider (in templates/layouts
):
- The root layout
root.html.eex
, which is shared between both LiveViews and regular views. - The app layout
app.html.eex
, which is just used by regular views (not LiveView!) - The live layout
live.html.leex
, which is used only by LiveView.
First, we need to set our root layout in the :browser
pipeline in lib/gallery_web/router.ex
, using the :put_root_layout
plug.
# lib/gallery_web/router.ex
defmodule GalleryWeb.Router do
use DemoWeb, :router
pipeline :browser do
...
plug :fetch_live_flash
...
plug :put_root_layout, {GalleryWeb.LayoutView, :root}
end
...
end
Then, we move our lib/gallery_web/templates/app.html.eex
layout to root.html.eex
, removing the get_flash(...)
part which is specific to regular views, and place <%= @inner_content %>
where we want to output our views.
Root layout
<!-- lib/gallery_web/templates/root.html.eex -->
<!DOCTYPE html>
<html lang="en">
<head>
...
<%= csrf_meta_tag() %>
</head>
<body>
<header>
...
</header>
<%= @inner_content %>
<script type="text/javascript"
src="<%= Routes.static_path(@conn, "/js/app.js") %>">
</script>
</body>
</html>
Then we create the App layout (lib/gallery_web/templates/app.html.eex
), just for regular views, where we copy the <main>
tag with get_flash
functions we had in the default layout.
<!-- lib/gallery_web/templates/app.html.eex -->
<main role="main" class="container">
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<%= render @view_module, @view_template, assigns %>
</main>
And finally the Live layout (lib/gallery_web/templates/live.html.leex
), for our LiveViews, where we use live_flash/2
function.
<!-- lib/gallery_web/templates/live.html.leex -->
<main role="main" class="container">
<p><%= live_flash(@flash, :notice) %></p>
<p><%= live_flash(@flash, :error) %></p>
<%= @inner_content %>
</main>
Just one last thing, if we want to use this live.html layout in our GalleryLive
view, we need to update our LiveView to pass the :layout
option to use Phoenix.LiveView
:
defmodule GalleryWeb.GalleryLive do
use Phoenix.LiveView, layout: {GalleryWeb.LayoutView, "live.html"}
...
end
We can now refresh our page and enjoy our LiveView with the template! If you want to know more on how layouts work with LiveView, take a look at the official Live Layout documentation.
What’s next
In the following article, the Primitives of Phoenix LiveView we explore the Phoenix LiveView primitives, understanding the magic behind LiveView while learning how we can build a simple counter.
Take also a look at the Phoenix LiveView documentation. It’s a great read which gives a solid understanding of how things work and which kind of features are available.
Another great resource is the chrismccord/phoenix_live_view_example GitHub repo, where you find easy code examples showing all the different LiveView’s features.