The last feature provided by HEEx is the idea of components. Components are pure functions that can be either local (same module) or remote (external module).
I actually had a bit of a hard time understanding this wording at first myself.
Heex “html elements” are just syntactic sugar for a function call, so “local” means the module that you are currently rendering the element in.
For example:
# Components
def FooWeb.Components do
use Phoenix.Component
def thing(assigns) do
~H"""
<div class="thing">I'm a thing</div>
"""
end
end
# LiveView
def FooWeb.FooLive do
use FooWeb, :live_view
import FooWeb.Components
# 👆 This brings in the function `thing` into the current (local) module
def render(assigns) do
~H"""
<.thing />
"""
end
end
And without import:
def FooWeb.FooLive do
use FooWeb, :live_view
def render(assigns) do
~H"""
<FooWeb.Components.thing />
"""
end
end
This is an BEAM term - it refers to the two ways of calling a function. In code:
defmodule FunctionExample do
def foo do
IO.puts "foo"
end
def bar
foo() # <= this is a "local" call
end
end
defmodule SomeOtherModule do
def other do
FunctionExample.foo() # <= this is a "remote" call
end
end
Where would thing/1 have to be defined so I could use <.thing /> in my templates (.heex files)? Also, your example is for a live view. Would it be the same for a regular view?
Regular views (and their related templates) are also modules. Given components are just plain old functions the difference between <Module.component /> and <.component /> is the same as needing to use Module.some_function() or some_function() in regular elixir code. The latter is only available for local functions or imported ones.
Should be the same for a regular views though honestly, my entire experience with Phoenix has been LiveView and the only reg view I’ve ever worked with is from phx_gen_auth which I don’t touch much other than the templates.
Ah, so @LostKobrakai, it is incorrect to say that an imported function is now local? I’ve actually been curious how that works under the hood. I assumed that the functions got compiled into the module—is that not the case? As I type that I feel like it must be the case, haha. If so, they are still considered “remote” since they weren’t defined locally?
import SomeModule, only: [foo: 1]
# in a function
foo(x)
The “local” looking foo(x) call is expanded to SomeModule.foo(x). It would substantially increase compilation times if functions were copied into any module they were imported into.