https://www.germanvelasco.com/blog/phoenix-1-7-is-view-less
German Velasco
Blog Testing LiveView TDD Phoenix Talks
Phoenix 1.7 is View-less!
December 30, 2022
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 inside GreetHTML, 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"""
Hello world!
"""
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:
Hello world!
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:
<%= greeting() %>
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.
Want my latest thoughts, posts, and projects in your inbox?
[ ]
Subscribe
I will never send you spam. Unsubscribe any time.
Home
Blog
Testing LiveView
TDD Phoenix
Talks
Twitter GitHub
Get in touch
(c) 2022 German Velasco