Shared templates between LiveView and static pages

Ever wanted to share some templates between your LiveViews and other static pages? Footers/headers/sidebars and such parts of a website can be shared. Here is something to help you.

You can define a view like this:

defmodule YourAppWeb.SharedView do
  use YourAppWeb, :view
end

and define some templates for it, like lib/templates/shared/header.html.leex. Note the leex extension, as this will be used by LiveViews too.

Now, you can render it as a normal view in your app.html.eex:

<%= render YouAppWeb.SharedView, "header.html", assign1: value1 %>

You can define a function like this somewhere (and import it in your my_app_web.ex in view_helpers function):

def render_shared(templates, assigns \\ []) do
  render(YouAppWeb.SharedView, template, assigns)
end

and replace the render call with this:

<%= render_shared "header.html", assign1: value1 %>

And that's it. A normal view is rendered in your static pages.

Now for the LiveViews, you can use a LiveComponent to render this view. Define a LiveComponent like this:

defmodule YourAppWeb.Live.Header do
  use YourAppWeb, :live_component

  def render(assigns) do
    render_shared("header.html", assigns)
  end
end

This way, LiveComponent renders the same view. Now, you can use it in your live.html.leex like this:

<%= live_component @socket, YourAppWeb.Live.Header, assign1: value1 %>

And that's it. You have a view rendered in both your static and LiveView pages.

There are some things to note here about assigns. When rendering a static page, you have a @conn that is a Plug.Conn, and when rendering a LiveComponent you have a @socket that is a Phoniex.LiveView.Socket. Also, @conn.assigns and @socket.assigns may not be available at all. Don't rely on the whole assigns when rendering your view. Instead, explicitly pass everything you need:

# In `app.html.eex`
<%= render_shared "header.html", current_user: @current_user %>

# In `live.html.leex`
<%= live_component @socket, YourAppWeb.Live.Header, current_user: @current_user %>

# in `header.html.leex`
<%= @current_user.name %>

Also, note that we're defining a stateless component. You can make your component stateful, but that would require some extra care in your view.

Bottom line is, this won't scale if your shared views are complex, with too much dynamicity. Keep them small, and mostly static, and this will help you DRY.