Dynamically render Surface components

Is there a nice way to dynamically render a Surface component? I actually don’t even really figure out a non-nice way atm.

I current have a main Page which contains the layout and I want to dynamically render content which would be components.

defmodule AppWeb do
  use Surface.LiveView

  data section, :string, default: "home"

  def render(assigns) do
    ~H"""
    <div>
      <header><h1>header</h1></header>
      <main>
        <!-- Is it possible to render a component based on the `section` data attribute here? -->
      </main>
    </div>
    """
  end

  def handle_params(%{"section" => section}, _, socket} do
    # Do some validation here to ensure `section` section is valid

    {:noreply, assign(:section, section)}
  end
end

Is there a better way of dong this? My first attempt involved having a Layout component with a slot, then each section is its own LiveView, but that felt a little heavy-handed. Each section is rich enough that I want them to be their own components, but not heavy enough that they warrant loading a new LiveView each time.

I feel like I’m missing something really obvious, but my focus is being pulled in all different directions in the past couple of weeks.

Thanks!

So, reading through the source of LiveDashboard I’ve come up with something.

Essentially:

defmodule AppWeb do
  use Surface.LiveView

  data page, :string

  def render(assigns) do
    ~H"""
    <div>
      <header><h1>header</h1></header>
      <main>
        {{ @page.render(assigns) }}
      </main>
    </div>
    """
  end

  def handle_params(%{"page" => page}, _, socket} do
    {:noreply, assign(:page, resolve_page(page))}
  end

  def handle_params(_, _, socket) do
    {:noreply, assign(:page, AppWeb.Pages.Home)}
  end

  defp resolve_page("foo"), do: AppWeb.Pages.Foo
  defp resolve_page("bar"), do: AppWeb.Pages.Bar
end

Is how I am doing this.

Nice. Thanks for the link!