This is episode #8 of a Phoenix LiveView series. Last episode we converted the new users page and form into live views. We also had a chance to see what LiveView form validations look like.
In this episode, we'll build the live views and templates for the show and edit pages, fix a few bugs and finish converting users from traditional CRUD to LiveView. After this episode, we won't need the user controller at all.
Note: This episode is using Phoenix LiveView, version 0.5.1. If you've been following along with the series you'll be fine but if you've already upgraded to a newer version you'll hit some issues. We'll be upgrading LiveView next episode.
The templates
The first thing we'll do is update the link
tags in our show.html.eex
and edit.html.eex
templates to use LiveView. That means changing link
to live_link
and changing path
to live_path
and updating the extension.
In edit.html.leex
<%= live_link "Back", to: Routes.live_path(@socket, UserLive.Index) %>
In show.html.leex
<%= live_link "Edit", to: Routes.live_path(@socket, UserLive.Edit, @user) %>
<%= live_link "Back", to: Routes.live_path(@socket, UserLive.Index) %>
Create the LiveViews
First, create user_live/show.ex
:
defmodule ReactorWeb.UserLive.Show do
use Phoenix.LiveView
alias Reactor.Accounts
alias ReactorWeb.{UserLive, UserView}
alias ReactorWeb.Router.Helpers, as: Routes
alias Phoenix.LiveView.Socket
def render(assigns), do: UserView.render("show.html", assigns)
def mount(_session, socket), do: {:ok, socket}
def handle_params(%{"id" => id}, _url, socket) do
if connected?(socket), do: Accounts.subscribe(id)
{:noreply, socket |> assign(id: id) |> fetch()}
end
def fetch(%Socket{assigns: %{id: id}} = socket) do
assign(socket, user: Accounts.get_user!(id))
end
def handle_info({Accounts, [:user, :updated], _}, socket) do
{:noreply, fetch(socket)}
end
def handle_info({Accounts, [:user, :deleted], _}, socket) do
{:stop,
socket
# |> put_flash(:error, "This user has been deleted.")
|> redirect(to: Routes.live_path(socket, UserLive.Index))}
end
end
This is very similar to the LiveView we created last episode. We've got a render function that just passes the template file and the assigns into UserView.render
, and we've got a mount function that returns our initial socket. As before, we're using the generated functions in our Accounts context to do our basic CRUD as well as the subscribe
function we added to it.
The major new piece for this file is the handle_info
function, which matches the assigns we got from the router, and gets the ID of the user being shown.
Our LiveView for edit.ex
is a bit more complex:
defmodule ReactorWeb.UserLive.Edit do
use Phoenix.LiveView
alias Phoenix.LiveView.Socket
alias Reactor.Accounts
alias Accounts.User
alias ReactorWeb.{UserLive, UserView}
alias ReactorWeb.Router.Helpers, as: Routes
alias Phoenix.LiveView.Socket
def render(assigns), do: UserView.render("edit.html", assigns)
def mount(_session, socket), do: {:ok, socket}
def handle_params(%{"id" => id}, _url, socket) do
if connected?(socket), do: Accounts.subscribe(id)
{:noreply, socket |> assign(id: id) |> fetch()}
end
defp fetch(%Socket{assigns: %{id: id}} = socket) do
user = Accounts.get_user!(id)
assign(socket, user: user, changeset: Accounts.change_user(user))
end
def handle_event("validate", %{"user" => params}, socket) do
cset =
socket.assigns.user
|> Accounts.change_user(params)
|> Map.put(:action, :update)
{:noreply, assign(socket, changeset: cset)}
end
def handle_event("save", %{"user" => params}, socket) do
case Accounts.update_user(socket.assigns.user, params) do
{:ok, user} ->
{:stop,
socket
# |> put_flash(:info, "user updated")
|> redirect(to: Routes.live_path(socket, UserLive.Show, user))}
{:error, %Ecto.Changeset{} = cset} ->
{:noreply, assign(socket, changeset: cset)}
end
end
def handle_info({Accounts, [:user, :updated], _}, socket) do
{:noreply, fetch(socket)}
end
def handle_info({Accounts, [:user, :deleted], _}, socket) do
{:stop,
socket
# |> put_flash(:error, "This user has been deleted.")
|> redirect(to: Routes.live_path(socket, UserLive.Index))}
end
end
All the pieces are familiar though. Like new.ex
, we need to pass a changeset to the template so form actions can make changes to our DB. We'll also need the same event handlers (but have them call update
rather than create
). Like show.ex
, we need to add handle_info
pages to reflect any external changes to the user being shown on the page.
Notifying Subscribers (Account Context)
Note that the Accounts.notify_subscribers/2
function broadcasts the module, the event which has just occurred and the "result" (AKA data needed by the LiveView) to all clients currently subscribed, and then it passes the exact same {:ok, result}
or {:error, reason}
tuple passed in as its first argument.
This is why we can drop the function into our CRUD pipelines without changing the data being passed through.
defp notify_subscribers({:ok, result}, event) do
Phoenix.PubSub.broadcast(Reactor.PubSub, @topic, {__MODULE__, event, result})
Phoenix.PubSub.broadcast(
Reactor.PubSub,
@topic <> "#{result.id}",
{__MODULE__, event, result}
)
{:ok, result}
end
defp notify_subscribers({:error, reason}, _), do: {:error, reason
(Source code available for premium members)
Next time, we'll update LiveView to version 0.8.1 and also get the /foo page updated.
No Comments