Use .html.heex file for Phoenix.Component template (instead of ~H)

Hello,

I’d like to use Phoenix function component but without the HEEX code being part of the module’s code - the HTML template is pretty long to be included in the Elixir module itself and would make the navigation and editing expierence somewhat hard.

Is there any way I could render the HTML from an external .html.heex file from within the function body, please?

Something like this

defmodule DashboardWeb.Components.Navigation do
  use Phoenix.Component

  def main_navigation(assigns) do
    # Render .html.heex file here instead of the ~H sigil
  end
end

Directory structure:

I haven’t found any information in the documentation or in the source code and I failed all of my tries rendering the file view Phoenix.View or LiveView or even via Phoenix.Template :sweat_smile:

In Phoenix.LiveComponent this was automatically done when the render function was not defined in the component module (via Phoenix.LiveView.Renderer.before_compile) but I can’t figure out how to elegantly do it in the function component world.

Thank you and have a nice one :slight_smile:

2 Likes

For the context: The reason I am migrating standard LiveView components to the function ones is that I’d like to reuse some of the templates in “dead views” as well - mainly the ones generated by phx gen.auth in Phoenix 1.6.0. The rest of the application is all in LiveView where I can stay with standard LiveView components.

Is my approach wrong? Should I just duplicate the templates and use standard Phoenix.View in the “dead view” part of the application?

Hello,
sure it functions

I do this in my code :

defmodule VtmeetWeb.Component do
  use Phoenix.Component
 
  def avatar(assigns) do
    Phoenix.View.render(VtmeetWeb.LiveView, "component/avatar.html", assigns)
  end
end

And my dir is
image

Hope this helps,
cheers
Sébastien

3 Likes

Thank you.

May I see how the VtmeetWeb.LiveView module looks like, please? Is that some kind of “root” module for the whole application?

I’d like to use the Phoenix.Component outside of specific LiveView context (page) . Basically rendering it in the templates/layout/live.html.heex file like so:

<main role="main">
  <DashboardWeb.Components.Navigation.main_navigation current_user={@current_user} />
  <%= @inner_content %>
</main>

and similarly in the templates/layout/app.html.heex template

Currently the components I used are within liveview. So I’ve not tried what you want to accomplish.

Here is my liveview module

defmodule VtmeetWeb.LiveView do
  use VtmeetWeb, :subtemplate_view
end

and the subtemplate_view def within vtmeet_web.ex :

  def subtemplate_view do
    quote do
      use Phoenix.View,
        root: "lib/vtmeet_web",
        namespace: VtmeetWeb,
        pattern: "**/*"

      # Import convenience functions from controllers
      import Phoenix.Controller, only: [get_flash: 1, get_flash: 2, view_module: 1]

      # Include shared imports and aliases for views
      unquote(view_helpers())
    end
  end

Note the pattern which allow to find the template in subdirectories (maybe that’s what is missing in your case).

Sébastien.

2 Likes

That somewhat helped but I am unable to render the template w/o a LiveView context as it needs @socket variable. Which means the function component in this form is unfortunately not usable in the dead layout (aka app.html.heex)

For now I decided to just duplicate the navigation HTML template for the non-LV part of the application.

Thank you for you help <3

Correction: I was actually able to make it work with the solution provided by @seb3s. :tada:

The @socket variable was not needed at all.

Thank you!

1 Like

This is an interesting approach, but I don’t think it’s right. It works ok for a single type of component with a single template, but as soon as I added multiple components, each with their own template, the compiler gave some strange errors saying the view module couldn’t be found.

It seems like there should be a simpler way, since stateful components have this behavior automatically.

Yes, in a nutshell this feature is not yet supported.

2 Likes

@josevalim Will this be added to the heex roadmap? Can’t find if this is being tracked.

1 Like

I was also trying to separate HEEX from the .ex file. The main reason was because Visual Studio Code doesn’t seem to handle intellisense pretty well in my current setup.

Since that separation doesn’t seem to be supported right now… if anybody’s got intellisense to recognize CSS classes, I’d be interested in knowing which extensions your using… along with any extra configuration that you made to get there.

While it’s not directly supported you should be able to do this in phoenix views with heex templates.

defmodule MyAppWeb.Components do
  use MyAppWeb, :view

  def component(assigns) do
    render("component.html", assigns)
  end
end

Yeah, unfortunately, I don’t have a solution for this either. One of my main concerns is that since the main idea is to split components per function instead of per module, the component organization started to become a problem.

@LostKobrakai I considered doing this but since @APB9785 commented that, there are issues with this approach I think we should wait for a proper solution.

2 Likes