Reusable Templates in Phoenix

January 17, 2017

If you’ve spent any time with React or its look-a-likes, you’ve probably realized that most web apps have a lot of duplication in their templates. This is particularly true of server-rendered apps.

Templates should be organized as a set of reusable components, like this:

<Tabs>
  <Tab name="All Products" />
  <Tab name="Featured" />
</Tabs>

This way, if you ever decide to change the markup for <Tab> or <Tabs>, you only have to change one place, and all the <Tabs> across your project will be updated automatically.

However, just because server-rendered apps aren’t usually organized this way doesn’t mean they can’t be. In Phoenix, it’s actually quite easy. Here’s all you need to do.

1) Create a ComponentView and a templates/component directory:

defmodule MyApp.Web.ComponentView do
  use MyApp.Web, :view
end

2) Define templates for each component you want to have. For example:

// templates/component/tabs.html
<ul class="tabs">
  <%= assigns[:do] %>
</ul>

// templates/component/tab.html
<li class="tab"><%= assigns[:name] %></li>

3) Use these templates whenever you want tabs:

<%= render ComponentView, "tabs.html" do %>
  <%= render ComponentView, "tab.html", name: "All Products" %>
  <%= render ComponentView, "tab.html", name: "Featured" %>
<% end %>

So far, so good, but it’s too verbose. Let’s add a ComponentHelpers module like so:

# views/component_helpers.ex
defmodule MyApp.Web.ComponentHelpers do
  def component(template, assigns) do
    MyApp.Web.ComponentView.render(template, assigns)
  end
  
  def component(template, assigns, do: block) do
    MyApp.Web.ComponentView.render(template, Keyword.merge(assigns, [do: block]))
  end
end

Then, import it into your view function in the MyApp.Web module:

def view do
  quote do
    # ...
    import MyApp.Web.ComponentHelpers
  end
end

Your markup can then be much more concise:

<%= component "tabs.html" do %>
  <%= component "tab.html", name: "All Products" %>
  <%= component "tab.html", name: "Featured" %>
<% end %>

There’s no reason you couldn’t shorten it up even more if you wanted:

<%= c "tabs.html" do %>
  <%= c "tab.html", name: "All Products" %>
  <%= c "tab.html", name: "Featured" %>
<% end %>

It’s still not as concise as React, but I think it’s close enough.

<Tabs>
  <Tab name="All Products" />
  <Tab name="Featured" />
</Tabs>

Obviously, this is a contrived example with very simple markup, so it might seem like too much effort. However, I think it’s a very useful approach whenever you’re dealing with complicated markup that you need to reuse often.

comments powered by Disqus