Cuando nos encontramos con una vista que tiene muchos elementos HTML, normalmente queremos separarlos en sus propios componentes para tener una mejor organización del código que facilite su comprensión y mantenimiento, así como desacoplar la lógica grande en piezas más pequeñas, y por qué no, reutilizar estos componentes en otra vista (muy al estilo del modelo mental de React.js). Esto se puede conseguir fácilmente utilizando LiveComponent de Phoenix LiveView.
El problema, es que muchas veces estos componentes comparten cierta lógica entre sí, y muchas veces dependen el uno del otro para saber si debe ser renderizado o no. A esto se le llama renderizado condicional, y con Phoenix LiveView podemos lograrlo manejando la lógica condicional en la LiveView padre, por medio de la función callback handle_info.
Caso de Uso
Supongamos que tenemos una vista para registrar un vehículo en un taller de mecánica. Esta vista se compone de dos formularios, uno para el registro del dueño del vehículo y otro para el registro del vehículo como tal. Cada formulario tiene su propia vista de registro independiente, y por ello usamos un LiveComponent por cada una de estos, dentro del cual se maneja su propia lógica de negocios independientemente.
Estas vistas deben ser renderizadas independientemente y en orden:
Una vez registrado el usuario, se le debe pasar el id del usuario al componente de vehículo, y así poder asociar exitosamente el usuario con el vehículo cuando estamos registrando el vehículo.
Cómo logramos esto? Vamos al código...
La lógica condicional la manejamos en la vista padre de los componentes usuario y vehículo, en este caso será la LiveView vehicle_live/index.ex y su respectivo archivo vehicle_live/index.html.leex donde se encuentran los elementos HTML a renderizar.
En el archivo vehicle_live/index.ex:
defmodule CarWorkshopWeb.VehicleLive.Index do
use CarWorkshopWeb, :live_view
alias CarWorkshop.{Vehicles.Vehicle, Accounts.Customer}
@impl true
def mount(_params, _session, socket) do
{:ok, assign(socket, customer: %Customer{}, view_to_show: :customer_view)}
end
@impl true
def handle_params(params, _url, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end
defp apply_action(socket, :new, _params) do
socket
|> assign(:page_title, "Register Vehicle")
|> assign(:vehicle, %Vehicle{})
|> assign(:customer, %Customer{})
end
@impl true
def handle_info({:customer_registered, customer, view_to_show}, socket),
do: {:noreply, assign(socket, customer: customer, view_to_show: view_to_show)}
end
En la función mount, le asignamos al socket la propiedad view_to_show por medio de la cual sabremos el componente a renderizar en vehicle_live/index.html.leex. Le damos un valor de :customer_view, haciendo que la primera vista en ser renderizada sea la del componente de usuario.
La función callback handle_info será la encargada de cambiar la propiedad view_to_show del socket, y así, se realizará el renderizado condicional de cada componente según los argumentos que le pasemos a esta función.
En el archivo vehicle_live/index.html.leex:
<%= if @view_to_show == :vehicle_view do %>
<%= live_component @socket, CarWorkshopWeb.VehicleComponent,
id: @vehicle.id || :new,
title: "Register Vehicle",
action: @live_action,
vehicle: @vehicle,
customer_id: @customer.id
%>
<% else %>
<%= live_component @socket, CarWorkshopWeb.CustomerComponent,
id: @customer.id || :new,
title: "Register Customer",
action: @live_action,
customer: @customer
%>
<% end %>
Con esto, ya tenemos renderizada la vista de usuario. Lo único que nos faltaría sería llamar al callback handle_info desde el componente de usuario una vez que se haya ejecutado toda nuestra lógica de negocio, y así, luego permitir el renderizado de la vista de vehículo.
Supongamos que queremos renderizar el componente de vehículo inmediatamente después que se registre el usuario exitosamente. Para esto, en el archivo customer_component.ex:
...
@impl true
def handle_event("save", %{"customer" => customer_params}, socket) do
case Accounts.create_customer(customer_params) do
{:ok, customer} ->
send(self(), {:customer_registered, customer, :vehicle_view})
{:noreply, socket}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, changeset: changeset)}
end
end
...
Usando la función send() hacemos que en el LiveView padre se ejecute el callback handle_info que cumple con el contrato de parámetros {:customer_registered, customer, view_to_show}. Del cual podremos saber el indentificador del handle_info a ejecutar (:customer_registered), el usuario recién creado (customer) y la vista a renderizar (:vehicle_view).
Esto es todo, tenemos renderizado condicional de componentes!
Para ver la implementación completa puedes visitar el repo:
santiagocardo / car-workshop
Car Workshop Managment Web App
CarWorkshop
To start your Phoenix server:
- Install dependencies with
mix deps.get
- Create and migrate your database with
mix ecto.setup
- Install Node.js dependencies with
npm install
inside theassets
directory - Start Phoenix endpoint with
mix phx.server
Now you can visit localhost:4000
from your browser.
Ready to run in production? Please check our deployment guides.
Learn more
- Official website: https://www.phoenixframework.org/
- Guides: https://hexdocs.pm/phoenix/overview.html
- Docs: https://hexdocs.pm/phoenix
- Forum: https://elixirforum.com/c/phoenix-forum
- Source: https://github.com/phoenixframework/phoenix
Top comments (0)