Skip to content

Commit 27208e1

Browse files
committed
Improve docs for comprehensions
1 parent d4a2c3a commit 27208e1

File tree

3 files changed

+62
-9
lines changed

3 files changed

+62
-9
lines changed

guides/server/assigns-eex.md

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,8 @@ part if it changes.
3737

3838
The tracking of changes is done via assigns. If the `@title` assign
3939
changes, then LiveView will execute the dynamic parts of the template,
40-
`expand_title(@title)`, and send
41-
the new content. If `@title` is the same, nothing is executed and
42-
nothing is sent.
40+
`expand_title(@title)`, and send the new content. If `@title` is the same,
41+
nothing is executed and nothing is sent.
4342

4443
Change tracking also works when accessing map/struct fields.
4544
Take this template:
@@ -87,7 +86,7 @@ Generally speaking, **data loading should never happen inside the template**,
8786
regardless if you are using LiveView or not. The difference is that LiveView
8887
enforces this best practice.
8988

90-
## Pitfalls
89+
## Common pitfalls
9190

9291
There are some common pitfalls to keep in mind when using the `~H` sigil
9392
or `.heex` templates inside LiveViews.
@@ -149,8 +148,9 @@ The same functions can be used inside function components too:
149148

150149
Generally speaking, avoid accessing variables inside `HEEx` templates, as code that
151150
access variables is always executed on every render. The exception are variables
152-
introduced by Elixir's block constructs. For example, accessing the `post` variable
153-
defined by the comprehension below works as expected:
151+
introduced by Elixir's block constructs, such as `if` and `for` comprehensions.
152+
For example, accessing the `post` variable defined by the comprehension below
153+
works as expected:
154154

155155
```heex
156156
<%= for post <- @posts do %>
@@ -238,6 +238,55 @@ is passed to each child component, only re-rendering what is necessary.
238238
However, generally speaking, it is best to avoid passing `assigns` altogether
239239
and instead let LiveView figure out the best way to track changes.
240240

241+
### Comprehensions
242+
243+
HEEx supports comprehensions in templates, which is a way to traverse lists
244+
and collections. For example:
245+
246+
```heex
247+
<%= for post <- @posts do %>
248+
<section>
249+
<h1>{expand_title(post.title)}</h1>
250+
</section>
251+
<% end %>
252+
```
253+
254+
Or using the special `:for` attribute:
255+
256+
```heex
257+
<section :for={post <- @posts>}>
258+
<h1>{expand_title(post.title)}</h1>
259+
</section>
260+
```
261+
262+
Comprehensions in templates are optimized so the static parts of
263+
a comprehension are only set once, regardless of the number of items.
264+
However, keep in mind LiveView does not track changes within the
265+
collection given to the comprehension. In other words, if one entry
266+
in `@posts` changes, all posts are sent again.
267+
268+
There are two common solutions to this problem.
269+
270+
The first one is to use `Phoenix.LiveComponent` for each item in the
271+
comprehension:
272+
273+
```heex
274+
<section :for={post <- @posts>}>
275+
<.live_component module={PostComponent} id={"post-#{post.id}"} post={post} />
276+
</section>
277+
```
278+
279+
Since LiveComponents have their own assigns, LiveComponents would allow
280+
you to perform change tracking for each item. If the `@posts` variable
281+
changes, the client will simply send a list of component IDs (which are
282+
integers) and only the data for the posts that actually changed.
283+
284+
Another solution is to use `Phoenix.LiveView.stream/4`, which gives you
285+
precise control over how elements are added, removed, and updated. Streams
286+
are particularly useful when you don't need to keep the collection in memory,
287+
allowing you to reduce the data sent over the wire and the server memory
288+
usage.
289+
241290
### Summary
242291

243292
To sum up:

lib/phoenix_live_view.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1680,6 +1680,7 @@ defmodule Phoenix.LiveView do
16801680
2. Each stream item must include its DOM id on the item's element.
16811681
16821682
> #### Note {: .warning}
1683+
>
16831684
> Failing to place `phx-update="stream"` on the **immediate parent** for
16841685
> **each stream** will result in broken behavior.
16851686
>

lib/phoenix_live_view/engine.ex

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -290,9 +290,12 @@ defmodule Phoenix.LiveView.Engine do
290290
]
291291
}
292292
293-
This allows live templates to drastically optimize
294-
the data sent by comprehensions, as the static parts
295-
are emitted only once, regardless of the number of items.
293+
This allows live templates to send the static parts only once,
294+
regardless of the number of items. On the other hand, keep in
295+
mind the collection itself is not "diffed" across renders.
296+
If one entry in the comprehension changes, the whole collection
297+
is sent again. Consider using `Phoenix.LiveComponent` and
298+
`Phoenix.LiveView.stream/4` to optimize those cases.
296299
297300
The list of dynamics is always a list of iodatas or components,
298301
as we don't perform change tracking inside the comprehensions

0 commit comments

Comments
 (0)