Currently, there are two ways to add on_mount
hooks to a liveview, you either add them in the live_session
macro, or inside each liveview using the on_mount/1
macro.
The order that they will be executed is first the hooks in the live_session
call, and then the on_mount/1
calls inside the liveview in order.
Also, live_session
calls don’t support having other live_session
calls inside them.
This is a bit limiting in some scenarios and make a lot of duplicated code in some complex routers.
Example
In my route, I want to have the following hooks being attached to the liveview on_mount:
“/auth
” route (contains routes to sign_in and sign_up):
- Hooks.GetLocale
- Hooks.GetTimezone
- {Hooks.Authentication, :not_authenticated}
"/admin
"route (contains routes that needs the user to be logged):
- Hooks.GetLocale
- Hooks.GetTimezone
- {Hooks.Authentication, :authenticated}
Notice that both routes require the Hooks.GetLocale
and Hooks.GetTimezone
on_mount
hooks.
So either I need to write two live_session
calls that repeats the common hooks in my router:
scope "/auth", Live.Auth do
live_session :not_authenticated,
on_mount: [Hooks.GetLocale, Hooks.GetTimezone, {Hooks.Authentication, :not_authenticated}] do
...
end
end
scope "/admin", Live.Admin do
live_session :authenticated,
on_mount: [Hooks.GetLocale, Hooks.GetTimezone, {Hooks.Authentication, :authenticated}] do
...
end
end
Or I can add Hooks.GetLocale
and Hooks.GetTimezone
to my live_view
function (because every liveview in my system should execute these two hooks anyway):
def live_view do
quote do
use Phoenix.LiveView,
layout: ...
on_mount Hooks.GetLocale
on_mount Hooks.GetTimezone
unquote(html_helpers())
end
end
And then my route becomes simpler:
scope "/auth", Live.Auth do
live_session :not_authenticated, on_mount: {Hooks.Authentication, :not_authenticated} do
...
end
end
scope "/admin", Live.Admin do
live_session :authenticated, on_mount: {Hooks.Authentication, :authenticated} do
...
end
end
This second example is better IMO since I don’t need to repeat a long list of on_mount
hooks that are common to all my liveviews, but they just works fine as long as no hook in my live_session
requires some value first set by these hooks set in the live_view
function.
The reason is that the live_session
hooks will be executed first and then the ones set in the live_view
function. So, for the /auth
route, the order would be:
- {Hooks.Authentication, :not_authenticated}
- Hooks.GetLocale
- Hooks.GetTimezone
If Hooks.Authentication
requires the locale
value, then it will fail because the Hooks.GetLocale
is being executed after it.
I see three ways to fix this, the first one is the first example I shown in this post, basically I repeat all the common hooks in all live_session
calls I have in my router.
This option can become a chore really fast if I have a bunch of common hooks that I want to add, makes the router harder to read and more verbose.
A second solution would be to add a prepend?
option to the on_mount/1
call, that way, I can write it like this:
on_mount Hooks.GetLocale, prepend?: true
on_mount Hooks.GetTimezone, prepend?: true
And with this I would get this order of execution:
- Hooks.GetTimezone
- Hooks.GetLocale
- {Hooks.Authentication, :not_authenticated}
The third solution would be to allow live_session
(or a simplified version of that that only allows to set the on_mount
option) to be defined inside another live_session
, for example:
live_session :common, on_mount: [Hooks.GetLocale, Hooks.GetTimezone] do
scope "/auth", Live.Auth do
live_session :not_authenticated, on_mount: {Hooks.Authentication, :not_authenticated} do
...
end
end
scope "/admin", Live.Admin do
live_session :authenticated, on_mount: {Hooks.Authentication, :authenticated} do
...
end
end
end
IMO that would be the best approach since it will allow to scope it the same way we do with the scope
macro.
What do you thing about this suggestion? Do you know another way to achieve the same?