Can't use put_layout with liveviews on router?

Hello !

I’ve been trying to figure out the best way to assign a layout to my liveviews loaded from the router. I have (at the moment) two distinct layout (in addition to the root, set with the plug put_root_layout) that i need to assign to two different list of liveviews.

I’ve tried to use put_layout in a specific pipeline that i then scope like this:

pipeline :live do
  plug :put_layout, {MyAppWeb.LayoutView, :live}
end

pipeline :live_settings do
  plug :put_layout, {MyAppWeb.LayoutView, :live_settings}
end

scope "/", MyAppWeb do
  pipe_through [:main_pipeline, :app]

  live "/", FirstLive
end

scope "/settings", MyAppWeb do
  pipe_through [:main_pipeline, :settings]

   live "/settings", SecondLive
end

I am aware of both options to either pass a layout option to the live macro, or declaring it like the phoenix generator does inside a quote with use Phoenix.LiveView, layout: {MyAppWeb.LayoutView, "live.html"} (note that i removed that one to instead do it in the router). I just find it clearer when used in the router through a pipeline.

I’ve found a few confusing things about this on the forum (like the now removed put_live_layout), so i’m not sure if doing the way i want just won’t work, or if i must have something on my side preventing it from working properly ?

Note: I’m on liveview 0.13.3. Haven’t seen any fix in the changelog on the newer versions, so i don’t think it’s a bug, unless i missed it.

In the code you shared, you are not actually using the pipelines :live and :live_settings, but :app and :settings.

Also, the :live layout will be picked up automatically as it’s the default. You don’t need to declare the :live pipeline.

pipeline :live do
  plug :put_layout, {MyAppWeb.LayoutView, :live}
end

Thanks for your reply.

That’s actually a typo, i’m calling the proper pipelines on the original code. Here’s the fixed version (can’t update the original post anymore), without the :live pipeline.


pipeline :live_settings do
  plug :put_layout, {MyAppWeb.LayoutView, :live_settings}
end

scope "/", MyAppWeb do
  pipe_through :main_pipeline

  live "/", FirstLive
end

scope "/settings", MyAppWeb do
  pipe_through [:main_pipeline, :live_settings]

   live "/settings", SecondLive
end

That doesn’t change anything though. It’s still not working, the layout for the “/settings” scope isn’t being picked up. That’s a proper way to do it though, right ? Like it’s an issue on my part, and not something i shouldn’t try ?

Can you share :live_settings template? Are you calling <%= @inner_content %> inside it?

Also, to confirm, in the :main_pipeline you have the put_root_layout plug call?

Both template (:live and :live_settings) call <%= @inner_content %>. If i set either one of them directly with use Phoenix.LiveView, layout: {MyAppWeb.LayoutView, "live_settings.html"} (or the “live.html” version), it works flawlessly.

Yes, the main pipeline uses put_root_layout (and that one gets picked up properly).

I went to see if put_layout was actually setting the layout properly, and it seems to be the case (the :phoenix_layout property is set to the given layout depending on the scope properly). Despite this, i end up with the root layout being loaded, but not the other one.

After searching in LiveView’s code, i just found this, which i think is related to my problem, with what the live macro in the router is doing: Github LiveView relevant bit.

It looks like put_layout is called again, removing any previous assignment. Can anyone explain the reasoning behind this ?