LiveView: Aplicación interactiva en tiempo real sin usar Javascript

cover

LiveView es una librería Elixir/Phoenix. Nos permite implementar aplicaciones interactivas sin usar javascript más allá de lo mínimo. En esta publicación, intentaré ilustrar cómo funciona, mostrando un caso de uso donde lo apliqué con éxito.

Propongo el siguiente escenario (caso común): necesitamos filtrar los datos de una base de datos y el tamaño de los datos es lo suficientemente grande como para evitar que se filtre directamente en javascript.

Hay 2 enfoques que vienen rápidamente a la mente (que supongo que el lector está familiarizado):

  1. Formulario enviado a través de http, siendo los campos los parámetros para filtrar
  2. Aplicación Javascript que usa ajax para recuperar la información de una API

El primer enfoque requiere una recarga de la página y no será interactivo en absoluto. El segundo enfoque no necesita recargar la página y se puede hacer que sea altamente interactivo, pero se agrega complejidad adicional ya que se necesita crear y mantener una aplicación separada para esta tarea aparentemente simple.

LiveView proporciona una tercera forma: escribir solo el código del lado del servidor, al tiempo que proporciona interactividad en tiempo real y evita la recarga de páginas. Puede encontrar información detallada en https://dockyard.com/blog/2018/12/12/phoenix-liveview-interactive-real-time-apps-no-need-to-write-javascript

El enfoque es básicamente el siguiente:

  1. El usuario envía una solicitud HTTP regular a la página donde se está utilizando LiveView
  2. La vista está montada (se llama a una función real llamada mount, inicializando la vista)
  3. El html se procesa y se envía al cliente.
  4. El cliente se conecta a LiveView a través de websockets
  5. Se establece una conexión con estado

Una vez que se establece la conexión, hay algunos eventos en el cliente que se propagan al servidor, el servidor actualiza el estado y luego se envía una diferencia mínima de DOM (diferencias de DOM con respecto a la versión anterior) al cliente para que se procese.

Me enfocaré en implementar el escenario propuesto. Para obtener instrucciones de instalación, lea la documentación. Además, utilizaremos una función llamada retrieve_items que opcionalmente recibe un mapa con los filtros para aplicar. Esta función no se implementará en el siguiente ejercicio, solo asumiremos que recupera correctamente la información de la base de datos y la filtra como se esperaba.

Hay 4 cosas que deben hacerse para implementar nuestro LiveView:

  1. Decidir desde donde se va a servir
  2. Añadir una función mount
  3. Modificar la plantilla para enviar los eventos del cliente
  4. Escuchar los eventos del cliente

Revisaré cada punto de esta lista en detalle.

Decidir desde donde se va a servir

Los LiveViews se pueden adjuntar a rutas, controladores o plantillas. Esta vez iremos con plantillas:

def index(conn, _params) do
  live_render(conn, MyApp.MyLiveView)
end

Añadir una función mount

La función mount es donde inicializamos el LiveView. Básicamente establecemos qué es un «estado conocido estable». En nuestro caso, simplemente podemos agregar un filtro en blanco (datos sin filtrar):

def mount(_params, socket) do
    my_items = retrieve_items()

    {:ok,
     assign(socket, %{
       items: my_items,
       filter_state: %{
         "param_a" => "",
         "param_b" => ""
       }
     })}
end

Modificar la plantilla para enviar los eventos del cliente

Agregue los eventos a la plantilla para que podamos reaccionar a los cambios:

<%= 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 %>

Escuchar los eventos del cliente

Agregue las funciones para reaccionar a los eventos del cliente:

  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

Y ya hemos terminado. El formulario tiene phx_change: filter cuando se declara. Estos valores le dicen a LiveView que llame al método handle_event con el parámetro filter. Podría tener varios de esos parámetros y reaccionar según sea necesario en esos casos.

Palabras finales

LiveView es un enfoque que está ganando terreno. Hay otras bibliotecas que intentan hacer algo similar en otros marcos (como, por ejemplo, Reactor para Django). Encontré que LiveView es muy fácil de trabajar, y la sensación que se tiene cuando se está del lado del usuario es que es rápido. Obviamente, este es un ejemplo muy simple, y se pueden implementar cosas más sofisticadas, así que pruébelo.

Hay un par de desafíos que enfrentar cuando se trabaja con este enfoque:

  • Este es un enfoque no tradicional, por lo que es probable que no esté familiarizado con él y que tenga que leer mucho sobre las tecnologías subyacentes para poder comprender bien la implementación.
  • Ya no está trabajando en el lado del cliente. No habrá Javascript por defecto. Hay formas de integrarlo con algunas tecnologías del lado del cliente, pero eso probablemente tomará más tiempo que hacerlo directamente con JS y ajax. Hay muchos otros beneficios que LiveView aporta, pero en este caso vale la pena evaluar cuáles son sus requisitos antes de continuar.

Hasta ahora, estoy satisfecho con lo que he podido lograr y tengo la intención de seguir usándolo en el futuro.