Converting Phoenix Views to LiveViews

This is a guide for converting an existing Phoenix view into a LiveView. From Chris McCord’s announcement of the library:

“Phoenix LiveView is an exciting new library which enables rich, real-time user experiences with server-rendered HTML. LiveView powered applications are stateful on the server with bidirectional communication via WebSockets, offering a vastly simplified programming model compared to JavaScript alternatives.”

I won’t be going into detail about how all the features work, but even if you’ve never used it before you should be able to follow along and get up and running easily. We will be building a dead-simple example in order to maximize the likelihood that you’ll be able to easily grok the steps and use them in your own projects. I’ll start by quickly bootstrapping a blog application with a single resource, posts.

Note:  If you are using a Phoenix version earlier than v1.5, make sure you

install liveview: https://hexdocs.pm/phoenix_live_view/installation.html#content

If your app already exists, skip ahead to step 1.

Step 0.5: Initialize example app

From your preferred directory, run this command:

mix phx.new blog --live

When asked if you want to fetch and install dependencies, say yes (Y)

Enter the blog folder and set up the database:

cd blog

mix ecto.create

Use the handy generator tool to create the Post controller, context and views:

mix phx.gen.html Posts Post posts title:string body:text

As prompted, add the posts resource to your router in the browser scope:

resources "/posts", PostController

Now run migrations:

mix ecto.migrate

Now you can boot up your server with mix phx.server and visit localhost:4000/posts to see your (empty) list of posts. Follow the links to create a new post and save it to the database. Back on the posts index page  you’ll see the default links for looking at, editing and deleting your new post. This is pretty much the simplest possible Phoenix app. Now here we go with the cool stuff: converting this default index page into a live view, so that when you create, update or delete posts any browser window that’s pointed at the page will update automatically, no refresh required.

1: Define live route

First we need to define a route to our live view. In your router.ex file, after pipe_through: :browser, add something like this:

live “/postslive”, PostsLive.Index

If you hit localhost4000/postslive in your browser right now you should get an error because the module BlogWeb.PostsLive is not available. So let’s go create it.

2. Define module

Create a directory under lib/blog_web called live. This is where your live-view related code will live. Add another new directory called posts_live . Within this directory add a file called index.ex. Inside that file define the module BlogWeb.PostsLive.Index like so:

defmodule BlogWeb.PostsLive.Index do
    use BlogWeb, :live_view

Now our module is defined, but we get an error from Phoenix because render is not implemented. We could add a render function to our module, satisfying the error, but we’re going to take a different approach.

3. Create live template

Create a file in the blog_web/live/posts_live folder called index.html.heex. Now, go into the templates/post folder (where the non-live views live) and copy the contents from index.html.heex into lib/blog_web/live/posts_live/index.html.heex. We have duplicated our old ‘dead’ view and must now plug in the wires to make the template work as a live view.

The first problem we must solve: since our view is no longer being rendered from the posts controller, we don’t have access to our lists of posts, @posts, like we used to. We need to get that data to the live view another way.

4. Assign resources to socket

Head back to the index.ex module and define a mount function. This function takes three arguments: params, session and socket. We’re only going to use the socket so you can prefix the others with an underscore to show that we don’t care about them. Manually assign our list of posts to the socket and then return the socket, like this:

defmodule BlogWeb.PostsLive.Index do
    use BlogWeb, :live_view
    alias Blog.Posts
    def mount(_params, _session, socket) do
        socket = assign(socket, posts: Posts.list_posts())
        {:ok, socket}

Now we have a new error from Phoenix: key :conn not found. This one is an easy fix.

5. Change mentions of @conn to @socket in live template

Go into the posts_live/index.html.heex template and replace all instances of @conn with @socket. Boom! Your live template should load without error and look exactly like your original index page. There’s only one problem: it isn’t live. In order to fix that we need to leverage Phoenix PubSub.

6. Enable PubSub subscription

Head to lib/blog/posts.ex and add this:

@topic inspect(__MODULE__)
def subscribe do
    Phoenix.PubSub.subscribe(Blog.PubSub, @topic)

This will allow our live view and other modules to subscribe to events that we broadcast. Speaking of which, let’s set  up the broadcast.

7. Broadcast changes

Still in lib/blog/posts.ex,  add a private function to broadcast changes:

defp broadcast_change({:ok, result}, event) do
    Phoenix.PubSub.broadcast(Blog.PubSub, @topic, {__MODULE__, event, result})
    {:ok, result}

Then go into your create_post function and call broadcast_change at the end, like this:

def create_post(attrs \\ %{}) do
    |> Post.changeset(attrs)
    |> Repo.insert()
    |> broadcast_change([:post, :created])

You can use this pattern wherever you want changes broadcasted. Create, update, delete functions are good candidates for broadcasting these messages, since other modules will likely want to respond to these changes..

8. Subscribe to broadcast

Now we can head back to posts_live/index.ex and subscribe to the broadcast we just set up. Add this to mount:


Our live view is now listening to events broadcasted from Posts, but we need to reflect those changes in the UI. One last step!

9. Handle Broadcast Messages

Add a new function to posts_live/index.ex called handle_info:

def handle_info({Posts, [:post | _action], _post}, socket) do
    socket = assign(socket, posts: Posts.list_posts())
    {:noreply, socket}

All we’re doing here is responding to an incoming broadcast from Posts by fetching the list of posts again and assigning it to the socket. While this is a basic example, you may add handle_info callbacks for any subscribed PubSub topics which are relevant to the live view. Pattern matching allows you to respond to broadcasted messages in a granular way.

Now if you have two browser windows pointed at /liveposts a post created in one window will automatically appear in the other. Pretty cool!


That’s it! Hopefully this simple example will provide a map for you to quickly implement live views in your  pre-existing Phoenix projects.  

Header Photo by Marek Piwnicki on Unsplash

You've successfully subscribed to SmartLogic Blog
Great! Next, complete checkout for full access to SmartLogic Blog
Welcome back! You've successfully signed in.
Unable to sign you in. Please try again.
Success! Your account is fully activated, you now have access to all content.
Error! Stripe checkout failed.
Success! Your billing info is updated.
Error! Billing info update failed.