Assigns and LiveEEx templates
All of the data in a LiveView is stored in the socket as assigns.
The Phoenix.LiveView.assign/2
and Phoenix.LiveView.assign/3
functions help store those values. Those values can be accessed
in the LiveView as socket.assigns.name
but they are accessed
inside LiveView templates as @name
.
Phoenix.LiveView
's built-in templates are identified by the .leex
extension (Live EEx) or ~L
sigil. They are similar to regular .eex
templates except they are designed to minimize the amount of data sent
over the wire by splitting static and dynamic parts and tracking changes.
When you first render a .leex
template, it will send all of the
static and dynamic parts of the template to the client. After that,
any change you do on the server will now send only the dynamic parts,
and only if those parts have changed.
The tracking of changes is done via assigns. Imagine this template:
<h1><%= expand_title(@title) %></h1>
If the @title
assign changes, then LiveView will execute
expand_title(@title)
and send the new content. If @title
is
the same, nothing is executed and nothing is sent.
Change tracking also works when accessing map/struct fields. Take this template:
<div id="user_<%= @user.id %>">
<%= @user.name %>
</div>
If the @user.name
changes but @user.id
doesn't, then LiveView
will re-render only @user.name
and it will not execute or resend @user.id
at all.
The change tracking also works when rendering other templates as
long as they are also .leex
templates:
<%= render "child_template.html", assigns %>
The assign tracking feature also implies that you MUST avoid performing direct operations in the template. For example, if you perform a database query in your template:
<%= for user <- Repo.all(User) do %>
<%= user.name %>
<% end %>
Then Phoenix will never re-render the section above, even if the number of users in the database changes. Instead, you need to store the users as assigns in your LiveView before it renders the template:
assign(socket, :users, Repo.all(User))
Generally speaking, data loading should never happen inside the template, regardless if you are using LiveView or not. The difference is that LiveView enforces this best practice.
LiveEEx pitfalls
There are two common pitfalls to keep in mind when using the ~L
sigil
or .leex
templates.
When it comes to do/end
blocks, change tracking is supported only on blocks
given to Elixir's basic constructs, such as if
, case
, for
, and friends.
If the do/end block is given to a library function or user function, such as
content_tag
, change tracking won't work. For example, imagine the following
template that renders a div
:
<%= content_tag :div, id: "user_#{@id}" do %>
<%= @name %>
<%= @description %>
<% end %>
LiveView knows nothing about content_tag
, which means the whole div
will
be sent whenever any of the assigns change. This can be easily fixed by
writing the HTML directly:
<div id="user_<%= @id %>">
<%= @name %>
<%= @description %>
</div>
Another pitfall of .leex
templates is related to variables. Due to the scope
of variables, LiveView has to disable change tracking whenever variables are
used in the template, with the exception of variables introduced by Elixir
basic case
, for
, and other block constructs. Therefore, you must avoid
code like this in your LiveEEx:
<% some_var = @x + @y %>
<%= some_var %>
Instead, use a function:
<%= sum(@x, @y) %>
Similarly, do not define variables at the top of your render
function:
def render(assigns) do
sum = assigns.x + assigns.y
~L"""
<%= sum %>
"""
end
Instead explicitly precompute the assign in your LiveView, outside of render:
assign(socket, sum: socket.assigns.x + socket.assigns.y)
Generally speaking, avoid accessing variables inside LiveViews. This also applies
to the assigns
variable, except when rendering another .leex
template. In such
cases, it is ok to pass the whole assigns, as LiveView will continue to perform
change tracking in the called template:
<%= render "sidebar.html", assigns %>
Similarly, variables introduced by Elixir's block constructs are fine. For example,
accessing the post
variable defined by the comprehension below works as expected:
<%= for post <- @posts do %>
...
<% end %>
As are the variables matched defined in a case
or cond
:
<%= cond do %>
<% is_nil(@post) -> %>
...
<% @post -> %>
...
<% end %>
To sum up:
Avoid passing block expressions to library and custom functions
Never do anything on
def render(assigns)
besides rendering a template or invoking the~L
sigilAvoid defining local variables, except within
for
,case
, and friends