I recently had the opportunity to use Phoenix 1.7 and LiveView .18.3 in a production environment and it was, in a word, amazing.
It’s hard to know where to start, as I’m all π over the whole experience, but this series will introduce some of the most important aspects of Phoenix 1.7 that you need to know going in.
While it’s pretty straightforward to upgrade to 1.7 from 1.6, and everything is backward-compatible (though you need to make some mods to use all the latest features), I’m demonstrating these new features in a fresh new project, and I recommend you do the same.
If you haven’t already, update your generator:
mix archive.install hex phx_new
Then start a new project:
mix phx.new hey_girl
(hey_girl is my own personal, more casual take on hello_world, but you can sub whatever greeting you’re most comfortable with)
If we look in our mix.exs
file, you’ll see that we are using Phoenix 1.7.0 and LiveView 0.18.3. You’ll also notice that TailwindCSS is now included by default in new installations, with no more dependency on npm or need to manually install.
So we’re off to a great start. Let’s keep exploring.
A new way of laying things out
The hey_girl_web
directory, which is currently simply a landing page for a new Phoenix project, looks like this:
lib/hey_girl_web
βββ controllers
β βββ page_controller.ex
β βββ page_html.ex
β βββ error_html.ex
β βββ error_json.ex
β βββ page_html
β βββ home.html.heex
βββ components
β βββ core_components.ex
β βββ layouts.ex
β βββ layouts
β βββ app.html.heex
β βββ root.html.heex
βββ endpoint.ex
βββ router.ex
On the top level of that directory, where we once found controllers
, templates
, and views
, we now just have two directories β controllers
and components
.
Let’s first dive into the new components
directory, where there are a few things worth noting:
app.html.heex
androot.html.heex
, formerly located intemplates
, now live within a sub-directory calledlayouts
. Also,live.html.heex
is gone.- There’s a new file called
layouts.ex
- There’s also a new file called
core_components.ex
(but let’s save that one for Part 2 of this series)
Goodbye Phoenix.View; Hello Phoenix.Component + Phoenix.Template
In previous versions of Phoenix, the view and templating were controlled by Phoenix.View
, while in Phoenix 1.7, Phoenix.View
has been removed as a dependency in favor of Phoenix.Template
, which uses function components as the basis for rendering.
*What exactly do you mean by that? Hexdocs says, “a function component is any function that receives an assigns map as an argument and returns a rendered struct built with the ~H
sigil:”
Basically, everything related to the view is wrapped in an H sigil now. That means that instead of having to build a table like render("table", user: user)
, you can do it in the pretty function component syntax, including accessing assigns with an @, e.g. <.table rows={@users}>
, across all types of views.
Let’s see what this looks like in practice
Start by opening components/layouts.ex
:
defmodule HeyGirlWeb.Layouts do
use HeyGirlWeb, :html
embed_templates "layouts/**"
end
The first line below the module definition invokes the using
macro of the HeyGirlWeb
module, and declares we are using the html
format, which we defined in our hey_girl_web.ex
file. This html
definition takes the place of old view
definitions.
We then invoke a new bit of functionality called embed_templates
that comes from the Phoenix.Component
dependency. embed_templates/2
is given a directory where our templates live, and then when we call a function, it will look for a match of that template name minus the format and engine. (We could also pass it an optional second argument with additional options.)
If we take a peek at hey_girl_web.ex, for example, we can see HeyGirlWeb.Layouts referenced in two places:
- In the
live_view
definition, where we now set it as a parameter ofPhoenix.LiveView
and it tells that LiveView to use theapp.html.heex
as the template
use Phoenix.LiveView,
layout: {HeyGirlWeb.Layouts, :app}
- And also in the controller definition, it tells the
html
layouts to use the same layouts file.
use Phoenix.Controller,
namespace: HeyGirlWeb,
formats: [:html, :json],
layouts: [html: HeyGirlWeb.Layouts]
As you can see, whether you’ve got a controller-based rendering (cough aka dead view cough) or a Liveview-based rendering, they all share the same function components and layouts! This is why we no longer need the live.html.heex
file: both types of views now share a unified layout coming from app.html.heex
.
Looking more closely at controller-based layouts
Our router defines our home page as the following,get "/", PageController, :home
In Phoenix 1.6, the controller would have called the view, HeyGirlWeb.PageController.render("home.html", assigns)
. But now, thanks to Phoenix.Template
, PageController
looks for a home/1
function component instead.
Take a look at page_controller.ex
, where things look very famliiar, but you’ll see a home/1
definition:
defmodule HeyGirlWeb.PageController do
use HeyGirlWeb, :controller
def home(conn, _params) do
# The home page is often custom made,
# so skip the default app layout.
render(conn, :home, layout: false)
end
end
The home
function component will use the the base layout from app.html.heex
because we are using the html
def set up in our web interface⦠that is, unless we set layout: false
, as we did in the controller above.
Moving forward, the view module should be named something like, HeyGirlWeb.PageHTML
or HeyGirlWeb.PageJSON
, depending on your view format, and it should be collocated in the same directory as your controller.
Notice the page_controller.ex
and page_html.ex
in the directory structure below:
lib/hey_girl_web
βββ controllers
β βββ page_html
| βββ home.html.heex
| βββ page_controller.ex
| βββ page_html.ex
Finally, if we open PageHTML
, we can see that we are using an html
format, and embed_templates
tells the app to look in the page_html
directory for the relevant template files.
defmodule HeyGirlWeb.PageHTML do
use HeyGirlWeb, :html
embed_templates "page_html/*"
end
So the PageController
uses the PageHTML
view module, which invokes the embed_templates
to set where your templates live. Then, in this case, we’re calling home
, so it looks in page_html
and finds the home.html.heex
template file!
Now if we want to add a controller-based “About” page, all we have to do is add an about
definition in our page_controller.ex
file,
def about(conn, _params) do
render(conn, :about)
end
Then we can add a route to it in our router (get "/about", PageController, :about
), and add a file named about.html.heex
in our page_html
directory. It will use the default app.html.heex
layout, and everything looks great.
Cool, but I β€οΈ LiveViews
Same, girl, same. π―ββοΈ
In Phoenix LiveView 0.18, the .heex
templates are colocated with our .ex
files.
Our file structure might look like this,
lib/hey_girl_web
βββ live
β βββ about
β βββ index.ex
| βββ index.html.heex
And in our if router we have live("/about", HeyGirlWeb.AboutLive.Index, :index)
, then Phoenix will automatically look for an Index module first before it tries for render/2
.
All our liveview lifecycle logic lives in the .ex
file, and we keep all the content we might normally have in the render function in the .heex
template file.
If we have other modules, such as Show, our file structure might look like the following:
lib/hey_girl_web
βββ live
β βββ about
| βββ index.ex
| βββ index.html.heex
| βββ show.ex
| βββ show.html.heex
And in our router, we would access the Show module like this:live("/about/:id", HeyGirlWeb.AboutLive.Show, :show)
One Step Further: Fun with Tabs
Let’s say we had a styleguide with a few tabs on it and we wanted to keep the content of our tabs in a directory together.
First, let’s think about how we might set up the router. Here are a few of the routes we could set up:
live "/styleguide/typography", StyleguideLive, :typography
live "/styleguide/colors", StyleguideLive, :colors
live "/styleguide/buttons", StyleguideLive, :buttons
And this is what our live/
directory looks like:
lib/hey_girl_web
βββ live
βββ styleguide_live.ex
β βββ styleguide
| βββ buttons.html.heex
| βββ colors.html.heex
| βββ typography.html.heex
Now let’s head into live/styleguide_live.ex
. We’re going to do a few things in this file:
- Set up
live/styleguide/
as our template directory for our.heex
files - Lay out a few “tabs” (which can just be styled links)
- Note that the links used below are coming from Phoenix.Component’s new
link/1
function, which we’ll cover more in detail in Part 3 of this series. For now, just know that this does the same thing aslive_patch
did before.
- Note that the links used below are coming from Phoenix.Component’s new
- Create a switch for the
live_action
assigns value.- This value is the third parameter in your
live
macro in your router, so inlive "/styleguide/colors", StyleguideLive, :colors
, the@live_action
is:colors
.
- This value is the third parameter in your
- In that switch, display the content of the correct template by simply calling the function component by name, for example <.colors /> will display the content of
colors.html.heex
.
defmodule HeyGirlWeb.Styleguide do
use Phoenix.Component
embed_template("styleguide/*)
def render(assigns) do
~H"""
<.link patch={~p"/styleguide/colors"} replace={true}>
Colors
</.link>
<.link patch={~p"/styleguide/typography"} replace={true}>
Typography
</.link>
<.link patch={~p"/styleguide/buttons"} replace={true}>
Buttons
</.link>
<%= case @live_action do %>
<% :colors -> %>
<.colors />
<% :typography -> %>
<.typography assigns={@assigns} />
<% :buttons -> %>
<.buttons />
<% end %>
</div>
"""
end
end
If you have some assigns data you wanted to use in your <.typography /> function component, just pass it along in your function component like the example above. You could also invoke it it like `<%= _typography(assigns) %>
.
Sounds like function components are where it’s at! Tell me more.
Now that we’ve got some base pages set up, it’s time to add components. In the next article, I’m going to discuss:
- Declarative Assigns
- Building Function Components
- How to best organize components with Phoenix 1.7
- Core Components
- New Phoenix.Component function components such as <.link />
We're building an AI-powered Product Operations Cloud, leveraging AI in almost every aspect of the software delivery lifecycle. Want to test drive it with us? Join the ProdOps party at ProdOps.ai.