LiveView is an Elixir/Phoenix library. It enables us to implement interactive applications without using javascript beyond the bare minimum. In this post, I will try to illustrate how it works, by showing a use case where I successfully applied it.
I propose the following (common case) scenario: We need to filter data from a database and the size of the data is big enough to discourage from filtering directly in javascript.
There are 2 approaches that quickly come to mind (which I assume the reader is familiar with):
The first approach requires a page reload and will not be interactive at all. The second approach does not need to reload the page and can be made to be highly interactive, but additional complexity is added as a separate application needs to be created and maintained for this seemingly simple task.
LiveView provides a third way: Writing server-side code only, while providing real-time interactivity and avoiding page reloads. You can find detailed information at https://dockyard.com/blog/2018/12/12/phoenix-liveview-interactive-real-time-apps-no-need-to-write-javascript
The approach is basically as follows:
Once the connection is established, there are some events in the client that propagate to the server, the server updates the state, and a minimal DOM diff (DOM differences with respect to the old version) is then sent to the client to be rendered.
I will focus on implementing the proposed scenario. For installation instructions, please read the documentation. Also, we will use a function named retrieve_items which optionally receives a map with the filters to apply. This function won’t be implemented in the following exercise, we will just assume that it correctly retrieves the information from the database and filters as expected.
There are 4 things that need to be done to implement our LiveView:
I will go through every point in this list in detail.
LiveViews can be attached to routes, controllers or templates. We will go with templates this time:
def index(conn, _params) do live_render(conn, MyApp.MyLiveView) end
The mount function is where we initialize the LiveView. We basically establish what a “stable known state” is. In our case, we can simply add a blank filter (unfiltered data):
def mount(_params, socket) do my_items = retrieve_items() {:ok, assign(socket, %{ items: my_items, filter_state: %{ "param_a" => "", "param_b" => "" } })} end
Add the events to the template so we can react to changes:
<%= form_for :my_filter, "#", [phx_change: :filter, phx_submit: :ignore], fn f -> %> <%= label f, :param_a, "Filter by param_a" %> <%= text_input f, :param_a, class: "form-control", "Search by param_a", value: @filter_state["param_a"] %> <%= label f, :param_b, "Filter by param_b" %> <%= text_input f, :param_b, class: "form-control", "Search by param_b", value: @filter_state["param_b"] %> <% end %> <%= for item <- @items do %> <%= item.name %> <% end %>
Add the functions to react to client events:
def handle_event( "filter", %{"_target" => ["my_filter", param], "my_filter" => my_filter}, socket ) do %{^param => value} = my_filter filter = Map.put(socket.assigns.filter_state, param, value) my_items = retrieve_items(filter) {:noreply, assign(socket, items: my_items, filter_state: filter )} end
And, we are done. The form has `phx_change: filter` when declared. These values tell LiveView to call the handle_event method with the `filter` parameter. You could have several of those parameters and react as necessary in those cases.
LiveView is an approach that is gaining traction. There are other libraries that are trying to do something similar in other frameworks (like, for example, Reactor for Django). I found LiveView very easy to work with, and the sensation you get when you are on the user side is that it is fast. Obviously, this is a very simple example, and more sophisticated things can be implemented, so give it a try.
There are a couple challenges to face when working with this approach:
So far, I am satisfied with what I have been able to achieve, and I intend to keep using it in the future.