Question regarding code organization/isolation best practices for a "spa-like" series of pages

Hello all. I have a question as to best practices for code organization when implementing a liveview with a lot of responsibilities.

Supposing I have a section of an application that has a persistent interface with several different pages rendered in a main section of the page:

┌─────────────────┐                               
│ my_app_live.ex  │                               
├─────────────────┴──────────────────────────────┐
│                 Persistent UI                  │
├────────────────────────────────────────────────┤
│                                                │
│                                                │
│     ┌─────────────────────────────────────┐    │
│     │                                     │    │
│     │                                     │    │
│     │ Main body updating via @live_action │    │
│     │         (ie. pseudo router)         │    │
│     │                                     │    │
│     │                                     │    │
│     │                                     │    │
│     └─────────────────────────────────────┘    │
│                                                │
│                                                │
└────────────────────────────────────────────────┘

where the router might look like:

scope "/", MyAppWeb do
  pipe_through :browser

  live "/", MyAppLive, :home
  live "/my-page", MyAppLive, :my_page 
  live "/authorize", MyAppLive, :authorize
end

and I want to isolate functionality so that I don’t have one gnarly live view. Specifically supposing I have a number of handle_params that are unique to an individual subview.

What’s the best practice here? In my mind, I want to isolate the functionality of each subview within its own file. This seems very easy to do with live components, but it doesn’t really solve the issue of the callbacks unique to the subview.

Supposing the persistent UI had some kind of heavy data requirement that you wouldn’t want to trigger again and again on page navigation (and some of the data should be shared with the subviews as well), how would you go about tackling the problem while also keeping clear delineation of responsibilities (for easier maintenance, testing, etc.)

I don’t know have any great suggestions for you, but I have similar concerns and I am inclined to believe the lack of answers here means no one has thought of anything much better than what you’re doing (parent live view, subview live components). I suspect that the complexity here is not accidental, but is tied to the FE presentation layer requiring a “pseudo router.” Obviously you could delegate the param logic to their “subviews” but there’d still be some coupling. Is there a more practical problem you’re running into? I don’t think the heavy data should be retriggered by a nav change necessarily (assuming they are in the same live session)

1 Like

Have you considered using multiple LiveViews within a live_session? That way each “subview” is just a LiveView with its own handle_params lifecycle callback. And by using live_session, you can also put shared mount logic into a shared on_mount hook for all the LiveViews in that live_session.

    live_session :default, on_mount: [MyAppWeb.SharedMount] do
      live "/", HomeLive, :home
      live "/my-page", PageLive, :my_page 
      live "/authorize", AuthLive, :authorize
    end

Naturally, there are some tradeoffs involved since each navigating within the live_session across its LiveViews will trigger mean another LiveView mount, but the websocket connection itself will be re-used across live navigation events. Plus, you could also check for no established socket via !connected? in the on_mount hook to conditionally run the shared logic that only needs to be run once at the beginning of the live_session.

1 Like