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
© 2018 Chris McCord
Licensed under the MIT License.
https://hexdocs.pm/phoenix_live_view/assigns-eex.html