Harnessing the Power of LiveView and Svelte for Complex User Experiences

An overhead view of a park with many walking paths converging

When you need engineering know-how to make your digital product a succes, we can help. Book a free consult today to learn how.

LiveView and Svelte may seem like either/or technologies. Completely different stacks that compete with each other to be the chosen technology for your next project.

But that’s not the case. You don’t need to choose between having the best development model for realtime applications and a modern JavaScript framework for complex user experiences.

LiveView and Svelte actually play very nicely together and they can be an awesome combination to get the best of both worlds.

LiveView sometimes is not enough. And that’s ok.

Phoenix’s LiveView is possibly one of the most productive ways of building rich realtime user experiences for the web, and soon for native apps too.

It brings massive gains in development pace because developers declare the relationship between the data and the UI and then let LiveView take care of keeping everything in sync.

Form submissions, button presses, and other usual ways of interacting with the app are a breeze to handle as events that modify the state of the app, and the UI changes caused by those changes flow from your servers to all the clients in realtime effortlessly.

But nowadays web apps have interfaces that rival native apps in richness and complexity and some of those are hard or inconvenient to model purely using LiveView: interfaces containing context-aware animations, canvases, drag and drop, contextual menus, video/audio interaction, or that in generally need to leverage JavaScript APIs very often. If yours checks several of these boxes you may think that LiveView is not the best choice, and you would be somewhat correct. While LiveView can handle many of these use cases, it’s also true, as is often the case when pushing the limits either on performance or features, that staying close to the metal and coding those features directly in JavaScript can be more advantageous.

Svelte is enough. Sometimes it can be too much (work)

At DockYard we’ve been developing Svelte apps since the early days of Svelte 3.0. Some of our Svelte apps are used by tens of millions of users daily and we love using Svelte for modeling very complex user experiences on the web.

So when we started planning a WYSIWYG visual editor for BeaconCMS that allowed users to craft their content using a drag-and-drop UI, we quickly identified that Svelte would be a better choice for building the complex interactions needed by that kind of editor.

We started building a standalone Svelte app for this editor. It was a pleasing experience and we iterated quickly while we mocked up any API endpoints we’d need to load components and pages and save them later after having made changes.

But soon enough, when we started integrating that proof of concept with our real back end, we found that designing an API that accommodated all the operations the editor needed to be able to perform-and also made sense from the back end/database perspective-wasn’t an easy task at all. Well-designed APIs take hard work and some iterations to get right.

We felt an unnecessary friction. We were happy working in our LiveView interface and we also enjoyed building a challenging UI editor with Svelte, but we didn’t enjoy nearly as much the task of integrating both.

In fact, having to develop a public-facing REST API (and the commitment to maintain it that goes along with building it) when we didn’t need it for the other 95% of the project didn’t feel right.

And then we found out how to have it all.

Entering live_svelte

live_svelte is a project created by Wout de Puysseleir that’s defined as “Svelte inside Phoenix LiveView with seamless end-to-end reactivity” and I think that while correct, it doesn’t capture the extent of how killer of a team both technologies form thanks to that library.

The idea is deceptively simple: You write your Svelte components as usual and you invoke them from within your perfectly normal LiveView app using the <.svelte> Phoenix component.

In our LiveView module, when we wanted to render our WYSIWYG editor for a page it was as simple as calling it, passing the @socket and any other props the component needs.

def render(assigns) do
  ~H"""
    <% # The rest of the template %>

    <.svelte name="components/UiBuilder" props={%{components: @components, page: @page}} socket={@socket} />
  """
end

From that point on, the best way to describe it is that you have an island of Svelte in a sea of LiveView. LiveView gives up control of a piece of the UI to Svelte, which takes care of all those complicated UI interactions. LiveView will, however, still forward any changes to the props passed into the Svelte component, and the Svelte components will receive those updates and react to them as they normally would if they were rendered inside a regular Svelte app.

However, the library has one more trick up its sleeve that seals the deal: sending events from Svelte to LiveView.

When a Svelte component needs to perform some business logic operation that belongs in the LiveView app it can use the live prop available in the top-level Svelte component to send an event back to LiveView.

<script>
    export let live
    export let page;
</script>

<div on:drop|preventDefault={() => live.pushEvent("update", selectedComponent)}>{page.template}</div>

This was a game changer for our productivity because, all of a sudden, we didn’t need to design a REST API to bridge the LiveView and Svelte worlds. We could handle those events like we handle the rest of the events of the app. Oftentimes, we could even use business logic that had already been implemented long ago without any changes.

It completely eliminated a whole category of problems integrating the builder to the point that the entire Svelte app was migrated from a standalone project into our LiveView application in a day while removing a lot of code in both code bases.

The app (beacon_live_admin) didn’t significantly increase the size of our JavaScript either, because Svelte is known for generating very optimized bundles.

It even enabled features that would have been extremely challenging using a REST API, like injecting page-optimized updated stylesheets after every update. Also, while we could have done (and did in the past!) wrap JavaScript libraries, I found this approach nicer, especially when your libraries (cof, cof, charting libraries) generate large SVGs, as it unburdens LiveView from needing to send massive SVG markup updates through WebSockets to multiple users. Now we can just send data changes and let the clients handle the rendering.

Conclusions

LiveView and Svelte, instead of being considered opposing technologies, should be considered complementary. It is hard to convey how enjoyable and productive it felt to build an app with both, picking and choosing where and when to get down to the JavaScript trenches and when to fly fast in the LiveView ship.

If you are uncertain whether LiveView is a good fit for your next project or maybe a JavaScript framework would be a better choice, get in touch with us.

Maybe it’s both.

Newsletter

Stay in the Know

Get the latest news and insights on Elixir, Phoenix, machine learning, product strategy, and more—delivered straight to your inbox.

Narwin holding a press release sheet while opening the DockYard brand kit box