Hmm, those are interesting scenarios that may push the current streaming functionality to the limit. I’m also interested in hearing what others have to say too when working with streamed nested resources, but here are some off the cuff approaches that come to mind…
So from what I see in the docs, the stream_insert
function can also update an existing item in the stream based on the resource id
. One approach for the first scenario would be to broadcast post change events when a tag gets added/removed and then update the “streamed” post with stream_insert
. The Managing State section covers this approach, just with updating assigns rather than streams.
The second scenario is definitely trickier depending on the requirements – if posts have a many to many relationship with tags, editing a tag on one post may require other posts that share those tags to be updated. And since, unlike with assigns, we can’t use Kernel.update_in for dealing with nested data, it might just be more sane to re-stream the preloaded posts entirely then to try and selectively update nested resources.
defmodule DemoWeb.PostLive.Index do
use DemoWeb, :live_view
...
def mount(_params, _session, socket) do
preloaded_posts = Repo.all(Post) |> Repo.preload([:tags])
# set up LiveView to listen for changes to its posts and tags
# even changes made by other users
for %Post{id: id, tags: tags} <- preloaded_posts do
MyApp.Endpoint.subscribe("post:#{id}")
for %Tag{id: id} <- tags, do: MyApp.Endpoint.subscribe("tag:#{id}")
end
{:ok, stream(socket, :posts, preloaded_posts)}
end
# message from PostComponent/PostFormComponent about post with a new tag sent via
# MyApp.Endpoint.broadcast("post:#{id}", {:updated_post, post})
# rather than `send(self(), {:updated_post, post})` so other users' LiveViews are notified
def handle_info({:updated_post, post}, socket) do
{:noreply, stream_insert(socket, :posts, post)}
end
# message from TagComponent/TagFormComponent about updated tag sent via
# MyApp.Endpoint.broadcast("tag:#{id}", {:updated_tag, tag})
# rather than `send(self(), {:updated_tag, tag})` so other users' LiveViews are notified
def handle_info({:updated_tag, _tag}, socket) do
preloaded_posts = Repo.all(Post) |> Repo.preload([:tags])
{:noreply, stream(socket, :posts, preloaded_post)}
end
...
end
# index.html.heex
<div phx-update="stream">
<%= for post <- @streams.posts do %>
<.live_component module={PostComponent} id={post.id} post={post} />
<% end %>
</div>
# post_component.html.heex
<%= for tag <- @post.tags do %>
<.live_component module={TagComponent} id={tag.id} tag={tag} />
<% end %>