Live View - refreshing assigns across templates

I am working on an app composed of mostly discrete components that I am trying to implement with Live View. For now I have built this as a parent live view and template that renders several child live views/templates. I like this because it separates template and event handler code into files that are logically related.

For example, one component is a list of items with an input field that can be used to filter the list. Another component is a form with checkboxes that can be used to update user preferences. When the controller renders the page for the first time, the assigns for all of these components are passed to the parent view via the session and the session is used to mount each child view - like this simplified example:

page_controller.ex

def index(conn, _params) do
  ...
  LiveView.Controller.live_render(conn, PageLive, session: %{prefs: prefs, items: items})
end

page_live.ex

def render(assigns), do: PageView.render("index.html", assigns)

def mount(session, socket) do
  {:ok, assign(socket, items: session.items, preferences, session.preferences}
end

index.html.leex

<%= live_render(@socket, PreferencesLive, session: @session) %>
<%= live_render(@socket, ItemsLive, session: @session) %>

items_live.ex

def render(assigns), do: PageView.render("items.html", assigns)

def mount(_session, socket), do: {:ok, assign(socket, session.items)}

def handle_event("update_items", value, socket) do
   ...
  {:noreply, assign(socket, :items, updated_items)}
end

preferences_live.ex mirrors items_live.ex - it handles an “update_preferences” event and renders preferences.html.

preferences.html.leex and items.html.leex are simple templates that only refer to @preferences and @items assigns, respectively.

So, when the user triggers an event in a given component, that child live view event handler is called and the child template is re-rendered.

My problem arises because the components are not completely discrete. In the context of the example above, it turns out that when the user’s preferences change, the data underlying the items list may change, but I don’t see a way to trigger a refresh of the items list view from the event handler in preferences_live.ex.

I assume I could make this all work by abandoning the idea of having separate templates for different parts of the page. I get that if it was all just one view that only the parts of the page with updated assigns get re-rendered; I just like organizing the code in smaller logical pieces. I’m hoping that there is some alternative structure for this situation that I should have used, or that I have just missed some obvious feature of Live View that would allow me to update the assigns for one child template in the context of the event handler for a different child template.

3 Likes

Should you use Phoenix.PubSub to send notifications about those events and receive them in each live view?

Keep in mind you can still have one live view and two templates inside the same live view.

2 Likes

That would work - thank you for that suggestion! It may be that my preference for having separate files is not strong enough to offset the effort associated with the extra wiring, but I see how this gives me a straightforward option.

I’m not sure what this means - it may be a lack of understanding of terms on my part. You have already given me one option but if you were willing to clarify this one so much the better. Below I am going to make some statements about what it could mean and why I don’t think it actually means that, but I also recognize that there’s a good chance these statements could be wrong so I appreciate any corrections.

I think of a “live view” as the module in items_live.ex in my example. That module implements render/1, where one can indeed render multiple templates, but the result will always just replace one template. So I could tell it to render items.html.leex and then render preferences.html.leex, but it would just render them one after the other and use that to replace whatever was in items.html.leex originally, as opposed to updating each template separately in their original places on the page. So that doesn’t really get me where I want to be in most cases.

I understand that the template a live view renders can in turn render multiple child views (in the way that index.html.leex does above), but if I tried to render the parent template from a child live view, it would set up a loop in which first the parent template would mount, and in doing so mount the child view, which would then try to mount the parent, and so on. So I assume this isn’t what that sentence means either. (As an aside I actually tried to do this at one point, and it is the only time since I started using Elixir that I was able to cause an ugly system crash, which was kind of cool).

Thanks again for your quick and helpful reply.

Currently you have:

<%= live_render "foo.html" %>
<%= live_render "bar.html" %>

But you could make it be:

<%= live_render "foo_bar.html" %>

And then inside foo_bar.html you have:

<%= render "foo.html" %>
<%= render "bar.html" %>

You need to ask yourself if you want to split the concerns by View (i.e. UI) or LiveView (i.e. both UI and event handling logic).

2 Likes

Thanks for clarifying - agree that would also be a step in the direction I want to go. There may also be other ways to organize the event handler code without separating it into multiple Live Views.

So, in this scenario, would foo.html and bar.html be using the assigns from foo_bar.html? In my scenario, I would like to be able to have the summary for Auction_1, Auction_2, Auction_n rendered in a grid, but with each auction having it’s own liveview. This way I don’t have to re-render all auction summaries in the grid when the topic of Auction_2 gets an update from its publisher.

Fairly new to Phoenix, and total liveview newb, so any help is appreciated!