Split a Phoenix LiveView into smaller, composable modules.
Parent modules
A parent module declares its submodules with delegate/2:
defmodule MyAppWeb.DashboardLive do
use MyAppWeb, :live_view
use LiveDelegate
alias MyAppWeb.DashboardLive.Projects
delegate(:projects, Projects)
def mount(params, session, socket) do
{:ok, socket |> delegate_mount(params, session)}
end
endSubmodules are mounted in declaration order. Each submodule's on_mount/3
returns the updated socket directly. When every submodule uses mount: false,
delegate_mount/3 returns the socket unchanged.
Submodules
A submodule declares where it belongs with path::
defmodule MyAppWeb.DashboardLive.Projects do
use LiveDelegate, path: [:projects]
def on_mount(_params, _session, socket) do
socket |> delegate_assign(%{items: []})
end
def handle_event("add", _params, socket) do
{:noreply, socket}
end
endThe path namespaces its assigns, events, and DOM IDs:
delegate_assign(socket, value)
delegate_event("add") # "projects:add"
delegate_dom_id("form") # "projects-form"Nested submodules
A module can be both a submodule and a parent:
defmodule MyAppWeb.DashboardLive.Projects do
use LiveDelegate, path: [:projects]
alias MyAppWeb.DashboardLive.Projects.Filters
delegate(:filters, Filters)
def on_mount(params, session, socket) do
socket
|> delegate_assign(%{items: []})
|> delegate_mount(params, session)
end
endThe nested module declares its complete path:
defmodule MyAppWeb.DashboardLive.Projects.Filters do
use LiveDelegate, path: [:projects, :filters]
def on_mount(_params, _session, socket) do
socket |> delegate_assign(%{})
end
endIts helpers now use the nested namespace:
delegate_event("change") # "projects:filters:change"
delegate_dom_id("panel") # "projects-filters-panel"Messages and options
Use info: to route process messages by their first tuple element:
delegate(:notifications, Notifications,
events: false,
info: [:notification_received],
mount: false
)For example, {:notification_received, notification} calls:
Notifications.handle_info(notification, socket)Use mount: false, events: false, or info: false when a submodule does not
need that responsibility.
Summary
Functions
Assigns a value at the module's configured path.
Assigns a value below the module's configured path.
Builds a DOM ID from the module's configured path.
Builds a LiveView event name from the module's configured path.
Functions
Assigns a value at the module's configured path.
Given:
use LiveDelegate, path: [:projects]This:
socket |> delegate_assign(%{items: []})is equivalent to:
socket |> assign(:projects, %{items: []})With a nested path such as [:projects, :filters], the value is assigned at
socket.assigns.projects.filters. The parent assign must already exist.
Assigns a value below the module's configured path.
Given:
use LiveDelegate, path: [:projects]This assigns the form under socket.assigns.projects.form:
socket |> delegate_assign(:form, form)A list can be used to update more deeply nested values:
socket |> delegate_assign([:form, :status], :ready)The configured assign and any intermediate maps must already exist.
Builds a DOM ID from the module's configured path.
Given:
use LiveDelegate, path: [:projects]This produces id="projects-form":
<.form id={delegate_dom_id("form")} for={@form}>
...
</.form>Nested paths are joined with hyphens. With
path: [:projects, :filters], delegate_dom_id("panel") returns
"projects-filters-panel".
Builds a LiveView event name from the module's configured path.
Given:
use LiveDelegate, path: [:projects]This button sends the "projects:add" event:
<button phx-click={delegate_event("add")}>
Add project
</button>Nested paths produce nested event names. With
path: [:projects, :filters], delegate_event("change") returns
"projects:filters:change".