Simple Phoenix LiveView App: Setup

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 with socket.assigns.msg set to "none".
  • Just like the ~E sigil we can use in standard view render functions to embed eex 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 written assigns.msg.
  • The phx-keydown attribute we set on the div listens for "keydown" events and passes them to handle_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)

Back to index