Phoenix 1.7 is View-less! 😱
Phoenix 1.7-rc’s announcement mentions that Phoenix is
dropping Phoenix.View
.
But don’t worry. It’s for a good reason.
Phoenix team unified the HTML rendering approaches whether from a controller request, or a LiveView.
So, controllers and LiveViews will both render function components!
Here’s how it works. 👇
Function components instead of views
Controllers now render templates via format-based Elixir modules (the announcement still calls them view modules).
Suppose we have the following route:
get "/greet", GreetController, :hello
That means we need a GreetController
with a hello
action (I know, it’s not
restful, but it’s just an example! 😉)
Our controller code doesn’t change (we could use the params
and define
assigns
, but you get the idea):
defmodule ScoutWeb.GreetController do
use ScoutWeb, :controller
def hello(conn, _) do
render(conn, :hello)
end
end
Now here’s where things get different.
Before Phoenix 1.7, our controller would expect a GreetView
with a
render("hello.html", assigns)
function, or our GreetView
would expect to
find a hello.html.eex
template under the views/greet/
directory.
Now, our controller expects us to define a GreetHTML
(notice the caps in
HTML
for the format we’re rendering). And it expects us to define that
view-like module next to the greet_controller.ex
module:
Additionally, we have collocated the view modules next to their controller files. This brings the same benefits of LiveView collocation – highly coupled files live together. Files that must change together now live together, whether writing LiveView or controller features.
The GreetHTML
needs to use ScoutWeb, :html
and:
- Define a
hello
function component insideGreetHTML
, or - Collocate a hello.html.heex template based on what
embed_templates
defines.
Let’s take a look at both.
Function component
We can define a hello
function component like this:
defmodule GreetHTML do
use ScoutWeb, :html
def hello(assigns) do
~H"""
<h1>Hello world!</h1>
"""
end
end
Notice how we can include the ~H
sigil directly inside the hello
function.
That’s because use ScoutWeb, :html
declares our GreetHTML
to use
Phoenix.Component
. If you’ve used LiveView, that should look eerily familiar.
But maybe inline components aren’t your style. No worries.
Embedding templates
Our GreetHTML
can specify where to find our templates (instead of relying on
the views/greet/
convention). But keep in mind that the idea is to collocate
our templates next to our GreetHTML
module!
So, we specify where to find the templates with the new embed_templates
macro:
defmodule GreetHTML do
use ScoutWeb, :html
embed_templates "greet/*"
end
We can now create a greet/hello.html.heex
template and include the following:
<h1>Hello world!</h1>
As you can see, everything is now defined under the controllers/
directory:
lib/scout_web/controllers
├── greet_controller.ex
├── greet_html
│  └── hello.html.heex
├── greet_html.ex
I’m not sure I like it all being under controllers
since that directory no
longer solely contains controllers, but maybe I’ll grow accustomed to it (or,
perhaps, we can find a different name in time). But I think it’s great to have
everything in the same place.
And I like that it works very similarly to LiveView—where we either render
the component inline via ~H
or create the template next to the LiveView by
default.
Did we lose any View goodness?
The beauty is that I don’t think we’ve lost the nice things from Phoenix.View
.
For example, if we reference a function in the collocated template, it should be
defined in that GreetHTML
module—which acts like our view modules did
before.
So, if we were to have the following in our hello.html.heex
template:
<h1><%= greeting() %></h1>
We would define that function inside the GreetHTML
module:
defmodule GreetHTML do
use ScoutWeb, :html
embed_templates "greet/*"
def greeting do
"Hello world!"
end
end
How do I migrate my codebase?
The changes in Phoenix 1.7 are backwards compatible. If you want to keep using
Phoenix.View
, you can do so by adding :phoenix_view
as a dependency.
But if you want to migrate to the new component-based format, you can follow
Phoenix.Views
’ guides on migrating to Phoenix.Component.