Shortcomings in LiveView - are there any I should look out for?

My point was not the LOC, but actually LiveView’s being totally another beast, you work with it totally different than people are used to work on web frameworks in general, so there is a learning curve.

Anyway, nice to know your go to strategy, maybe I just am being a bit averse to the change of mind, will try out a side project using only LiveViews.

Just wondering now: is the plan in the future to extract Phoenix.Controller and Phoenix.View out of phoenix? I mean when (and if) LiveView gets adopted and people use the way you say (no dead view and controllers at all), there is no point on having those modules on the main phoenix app.

1 Like

It’s a different paradigm, so there are some ideas to learn, but I think it’s a vastly simpler model for newcomers vs traditional web development. A large number of abstractions fall away entirely, and there is in general less to think about, less to name, and less plumbing involved.

Not going to happen. Note that controllers and views are still the goto for API development and pages that require writing to the cookie session.

2 Likes

Yeah, so my “shortcoming” could be rewritten as: “it’s hard to convince old web developers to adopt” :joy:. Anyway, nice job so far, I have being watching the progress on it for some time. I have to tell you my expectations were high about LV, so recently tried it out for a demo on a talk I gave and expectations were met and exceeded (for the ones who are wondering, here is the project repo and you can check it out live, details on the readme repo)

Oh, true, cookies… :smiley: Thanks for all the answers

3 Likes

I very much love that LiveView:

  • allows you to create interactive forms/components without having to think about the API to load content from your server asynchroniously at all. Being able to create search forms, sortable/filterable tables etc. without needing to think about that is a very big win.
  • makes it very simple to synchronize state between multiple pages of the same user (say: what someone has in their shopping cart) or also different users (collaborative dashboards).

The main thing that I’d like to see improved in LiveView right now, is to have a slightly larger well-defined JS-API, which would allow other developers to step up to create their own integrations with it.
For instance, right now it is only possible to hook your LiveView elements to one phx-hook. This is fine if the final developer is the one supplying custom JS code, but it very much hampers the creation of re-usable add-on libraries.

I however have no doubt that this will improve, in one form or another, as LiveView matures.

7 Likes

Yeah, I’d kill for a good way to get JSON data into a hook. Embedding it in the DOM is super error prone, you gotta escape stuff and then unescape stuff, and it dies in a fire if you have a large number of data points.

11 Likes

This would answer a lot of issues but just copying/paste gives me an error: lib/liveapp_web/templates/layout/live.html.leex:52: undefined function connected?/1
The function should be available be default on the second mount inside live.html.leex, right?
Maybe the problem is the first render but what do I need to do? As you have it working I didn’t even though about that…

And to get things even more fuzzy when in one of my live page’s template I have a simple script <script>alert("lol")<script>
of course it works, but when I wrap it inside

<%= if connected?(@socket) do %>
   <script>alert("lol")</script>
<%end%>

it stops working although it’s in the page and I can see it when inspect…
Again, the first and second rendering confusing me…Fundamentals of passing data from the Plug connection to the LiveView

live.html.leex belongs to the LayoutView. So this module needs to import the LiveView functions or you have to do if Phoenix.LiveView.connected?(@socket) do.

3 Likes

Here is my summary of the impact of latency on LiveView. I live in South Africa and our servers is in Europe, I have a 150-200ms ping to them. Our entire app is built in LiveView.

Initial page load: The first response is received in 200ms, the page is not actually “live” yet for another moment while the websocket connects, but users won’t really notice it because they don’t start interacting with the page that quickly. If all links are live_redirect and live_patch then initial page load really only happens the very first time you open the page. SPAs will also have a delay the very first time. This is a non issue.

live_patch and other “same view interactions”: If you click a link or interact and the result is shown within 200ms it feels fast. I would rate live_patch links as giving a good experience even with 200ms latency. Other interactions on the same LiveView (phx_change, etc.) give the same good experience. Again LiveView is the same as an SPA if you ask me.

live_redirect: This is where the problem comes in. You can only use live_patch within the same LiveView. The live_redirect links unfortunately seem to take about 2x or 3x latency. It first sends a phx_leave that takes a full latency trip, then sends a phx_join to open the new page. With a 200ms ping this feels slow and is slower than normal links, but if you revert to normal links then you have the initial page load every time. I think if live_redirect can be adjusted to take one round trip (perhaps a phx_leave_and_join event?), then latency really isn’t an issue for LiveView. The way it is now, it is unfortunately an issue without a good alternative. I’ve logged an issue here:

phx_leave doubles latency on live_redirect links · Issue #1334 · phoenixframework/phoenix_live_view · GitHub

11 Likes

I have posted this on the issue, it is best if we continue the discussion there, but for completeness here is my reply:


To clarify, you should see this:

  1. We do an AJAX request which returns the new LiveView session in its response
  2. Once we get the session on the client, we send a command on the websocket to start the new LiveView

So it is 1 AJAX req/resp and a websocket req/resp - so 2 round-trips.

Chris and I have discussed an alternative solution to this problem - which takes some development to implement but seems to be doable - which is to use message passing to trigger the redirect within the node/cluster. We will aim for something like this:

  1. We do an AJAX request, which will contact the current LiveView and ask it to start a new LiveView with the session (this is a fire-and-forget). We will still return the session.
  2. Once we get the session on the client:
    • If we were already redirected, because of the server message, to the new LiveView, there is nothing to do
    • Otherwise we send a command on the websocket to start the new LiveView (the transport will be the source of truth in case it is racing as the new LiveView may actually already be in flight)

In the best case scenario, this should cut the latency in half (so one regular req/resp) and reduce the amount of requests in half compared to what we do today. The complexity is implementing a atomic replace channel operation on the transport but that should be doable.

11 Likes

One thing i’ve had a little issue with is that say i’ve got a page containing faceted navigation (that is like a online shop where you have filters on the left), and i add the phx-click-loading class to the buttons, i have no pure css way to also change the look of other parts of the page.

So say I select “Clothing Type: Tshirt” in the faceted nav I can make the cursor go to a pointer while it’s loading but i can’t make the list of products grey out while the result of the button click is loading.

From my experiments it seems like phx-page-loading doesn’t get added but what would be great is another class at the top level like phx-page-element-click-loading (with a better name than that!) while any click is loading.

Apologies if there is a way to do this that I’ve missed.

Just a correction on my previous post: after further testing I can confirm it’s 2x latency not 3x. Even with 2x latency the user experience seems as fast as a normal http request, so it’s not worth performing full requests instead of live_redirect to try and improve performance.

I also don’t show NProgress on first page load. The browser will already show a tab loading indicator so the extra NProgress doesn’t seem necessary and is perhaps a bit overzealous in terms of how many things show loading progress.

Secondly if you run a global app it’s a good idea to choose a hosting location that gives good pings to most of your users. You can’t just host in the US and expect everyone to have a good experience regardless of your web framework. Also if you pass all traffic through something like AWS Cloudfront or Global Accelerator it will make the browser connect locally (perform the SSL handshake with low latency, etc.) then use an already active persistent connection within the AWS network to your server. Not to dismiss previous comments in this thread, but if your websockets are taking 1s+ to connect you have an opportunity to improve.

2 Likes

One way to do it:

window.addEventListener("phx:page-loading-start", (info) => {
  if (info.detail.kind === "initial") {
    // Add your loading CSS class to html body or wherever.
  }
})

window.addEventListener("phx:page-loading-stop", (info) => {
   if (info.detail.kind === "initial") {
       // Remove CSS class.
    }
})

Sounds brilliant. I was thinking of making a PR but I don’t think this is a newbie level PR.

Sorry I don’t think I was that clear. I can get the page load events, but i can’t get a top level css class on any button click in progress. It gets added to the element I click but not the top level as well.

How does this not solve your requirement?

window.addEventListener("phx:page-loading-start", (info) => {
  if (info.detail.kind === "initial") {
    window.document.body.className = "some-top-level-loading-class"
  }
})

Okay so i modified it slightly in case I didn’t understand it.

window.addEventListener("phx:page-loading-start", (info) => {
  if (info.detail.kind === "initial") {
    window.document.body.className = "some-top-level-loading-class"
    console.log("adding class")
  }
})

I added an element

<button phx-click="somebutton">hi there</button>

and a handler

  def handle_event("somebutton", %{"value" => ""}, socket) do
    :timer.sleep(10000)
    {:noreploy, socket}
  end

I click the button, the button gets the class “phx-click-loading” and the console doesn’t log anything. I don’t know what your code is trying to do but it doesn’t get executed when I click the button.

What I’ve done in a similar situation is have the <form> tag contain my collection items and then simply use css cascading rules to display the loading placeholder on .phx-change-loading class that LiveView adds to the form’s class on each change.

To achieve the same effect for the initial render of the collection you can use .phx-disconnected to display the loading placeholder. LiveView will replace it with .phx-connected once connected.

CSS pseudo-code below :laughing:

/* default state */
.placeholder-item {
   display: hidden;
}
/* show the loading placeholder */
.phx-disconnected .placeholder-item,
.phx-change-loading .placeholder-item {
   display: block;
}

“initial” is perhaps the wrong event. Try “element” or remove that IF on info.detail.kind just to catch all.

https://hexdocs.pm/phoenix_live_view/js-interop.html#loading-state-and-errors

1 Like

I put a breakpoint at the start of the callback and it only triggers on page load, not when a button is clicked. I believe it would also fire if the button clicked would trigger a page navigation event - maybe that’s what you are saying?

Thanks for your help.

I will read through the code later and see if i can work out an alternative event as well. My main reason for posting here isn’t to ask for some code to add my own handlers, I just wanted to see if other people had this problem and if there is a common pattern which should be baked into LiveView.