DRY your Phoenix Templates
Modern web development has brought a lot of changes. Something I really like is breaking your templates in small components. This is something that I had a hard time to reproduce with Phoenix but I think I have now found all the small tricks to write powerful and well-structured components. I hope this will help you too.
Setup the stack
I've been working a lot with Typescript, something I like to have on all my projects. The same apply for SCSS. Finally, since we will be building components oriented templates, Tailwind fits perfectly on our front-end stack.
Phoenix 1.6 has been out for a while now, so we will setup all of this with eslint only.
First, add tailwind
and dart_scss
to your mix dependencies:
After you have installed the dependencies, you can open your assets
folder and apply the following changes:
mkdir scss ts
: create the folder that will host your typescript and scss filesmv js/app.js ts/phoenix_core.js
: you can remove the code related to topbar but keep the remaining code since it does the setup of live views. We will insert it later on in our typescript app.echo 'import "./phoenix_core"' > ts/app.ts
setup your main typescript filetouch scss/app.scss
: create the entry point of your style- optional:
cat css/*.css > scss/phoenix_core.scss
migrated the existing css into a new scss file. You can then import this newly created file intoscss/app.scss
- Finally, create a new file
tailwind.config.js
and another filescss/tailwind.css
and copy/paste the following code:
Note: you now have the main files for your typescript and scss. You are free to organize your typescript and style as you want.
Now you have all the correct files are the correct place, let's set up all the pipelines.
In config/config.exs
, change the eslint
configuration to target your typescript file instead of the initial javascript one:
And add the following lines right after the eslint
configuration:
You can see we generate 2 different css files. One containing our own scss named app.css
and another one containing tailwind, named tailwind.css
. So now we need to edit root.html.heex
and add also insert the tailwind.css
file.
We are almost there! But we need to configure the watchers to enable live reload on ts and scss edition. To do, edit the watched
part of the eslint
configuration available in config/dev.exs
and to make it looks like the following:
And finally, edit the deploy
command to properly build all the assets
when deploying your application in production. In mix.exs
, edit the alias deploy
to the following:
Alright! We did it ! We now have a Phoenix application with a great pipeline and no more node_modules
folder!
Write faster, with Helpers
I use Helpers to write very small pieces of HTML that I use a lot. For this example, I will write some helpers that I keep in lib/app_web/helpers/
. This file generates elements such as buttons and icons:
You can install material-icon
as you want. And now, let's make our helpers easy to use:
In lib/app_web.ex
, edit the view_helpers
function and add the following line:
I like to use an alias instead of import so I can have many helpers files and there won't be any name conflicts.
Once this is done, you can test the newly created helpers. In your templates, add:
<%= Helpers.Html.icon "home" %> <!-- generate a material icon -->
<%= Helpers.Html.button "hello", :primary, class: "mt-8" %> <!-- generate a button -->
<%= Helpers.Html.icon_button gettext("delete entity"), :warn, "delete" %> <!-- generate a button with an icon -->
Of course, those are just some examples I am using. But you are free to edit/extend this file.
Do more, with components
Alright. This is the exciting part of this article. Phoenix components are AMAZING. But they sure do lack some documentation. Let's see how they work by building a list
that will look like this:
First, let's create a new file list.ex
in a new folder named components
:
For components to work, they need to be imported into our view. To do so, once again, edit lib/app_web.ex
and add the following line to the view_helpers
:
import DAASWeb.Components.{List}
Note, I am using {}
because you will need to add all new components you will create to this line.
Once this is done, you can invoke the list component in your template:
<.list>
</.list>
Ok, we now have the core of our components. However, we now want to send the items to the list and then define the content of the row.
You can see with have updated the component to add the style of our list but the most important line is this one:
<%= render_slot(@inner_block, item) %>
I suggest you read the official documentation to learn more about render_slot
but this example should cover most of your needs. Basically, render_slot render a piece of HEEX template. @inner_block
contains the code inserted directly while declaring the components. And the second parameter, here item
will be sent to the template and can be retrieved using let
.
To clarify all of this here is how to invoke and use our list
:
<.list items={@users} let={user}>
<div class="flex">
<span class="flex-grow">
<%= Helpers.User.name(user) %>
</span>
<div class="actions">
<a href="#"><%= icon_action "send email", "email" %></a>
</div>
</div>
</.list>
And voila! Our list has been generated and we can reuse the list with anything since we can easily customize the template of the row.
Use your view!
Finally, any time you would copy/paste something inside your template, use your view instead! For example, my layout view contains a function menu_entry
that simply.. renders a new menu entry:
and then, in my HTML:
Yes, it is that easy!
Now you have all the keys to properly break down your application into small helpers and components.
If you have a problem and no one else can help. Maybe you can hire the Kalvad-Team.