Phoenix 1.7 is View-less! 😱
Phoenix 1.7-rc’s announcement mentions that Phoenix is
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
hello.html.eex template under the
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
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.
GreetHTML needs to
use ScoutWeb, :html and:
- Define a
hellofunction component inside
- Collocate a hello.html.heex template based on what
Let’s take a look at both.
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
use ScoutWeb, :html declares our
Phoenix.Component. If you’ve used LiveView, that should look eerily familiar.
But maybe inline components aren’t your style. No worries.
GreetHTML can specify where to find our templates (instead of relying on
views/greet/ convention). But keep in mind that the idea is to collocate
our templates next to our
So, we specify where to find the templates with the new
defmodule GreetHTML do use ScoutWeb, :html embed_templates "greet/*" end
We can now create a
greet/hello.html.heex template and include the following:
As you can see, everything is now defined under the
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
Did we lose any View goodness?
The beauty is that I don’t think we’ve lost the nice things from
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
So, if we were to have the following in our
<h1><%= greeting() %></h1>
We would define that function inside the
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.