weakwire

weakwire

LiveSub - PubSub-like functionality for LiveComponents without dealing with the LiveView

Hello people,

Big fan of elixir & phoenix LiveView.

While designing LiveView applications I use LiveComponent extensively.

Issue-Inspiration

I like LiveComponent to manage their state, but even with stateful components it is hard to abstract them away from the LiveView.

Usually there is a dependency to the LiveView (along with boilerplate code) when you want to talk to a different LiveComponent in the page, initialize the state of the component or make an async call.

That makes it harder to reuse the LiveComponent, test it, and the complexity of the LiveView increases by adding more LiveComponents.

Proposed solution

LiveSub https://gist.github.com/weakwire/d28dc8e5f9aa1edef78017ee308e7022 introduces a way for LiveComponents to talk to each other via a pub/sub like mechanism for the LiveView

Here is a LiveView application example, using LiveSub. https://github.com/weakwire/live_sub_example.

How to Use

  1. Use LiveSub in our LiveView and add your components with the init=true attribute
defmodule LiveSubExampleWeb.MyLiveview do
     use LiveSubExampleWeb, :live_view
     use LiveSub.LiveView
            ...
     ~H"""
          ...
         <.live_component module={PeopleComponent} id={PeopleComponent} init={true} />
          ...
       """
...
  1. Define the “topics” that your LiveComponents are subscribed_to and/or the topics your LiveComponent emits
defmodule LiveSubExampleWeb.PeopleComponent do
  @moduledoc false
   use LiveSubExampleWeb, :live_component
   use LiveSub.LiveComponent,
       subscribe_to: [
           "person_added",
            "people_loaded"
          ],
         emits: [
           "people_loaded"
          ]
  1. When you define a topic in “emits”, a helper function is created and you can publish your message like so:
SubHelper.pub_person_added(%{name: "John", surname: "Smith"})
  1. To listen to a message, a local function is called inside the LiveComponent, generated using the topic name from the subscribe_to parameter
defmodule LiveSubExampleWeb.PeopleComponent do
        ....
        #This will be called when `SubHelper.pub_person_added` is called from a component
        def sub_person_added(person, socket) do
          socket |> assign(:person, person)
        end

Result

  • Adding N LiveComponents to a LiveView doesn’t increase the complexity of the LiveView
  • Each LiveComponent is isolated and testable on it’s own.
  • LiveComponents can be reused with minimal effort. For eg. an “Edit Person” component can by used in multiple LiveViews that show a person with 0 changes.
  • The LiveComponent can initiate it’s state, even if it requires an async call
  • Less boilerplate & complexity, inspires people to use more LiveComponents in their LiveViews

Drawbacks

  • Currently you need to initialize the LiveComponent using init=true param. (For LiveSub to work, it requires the component id that unfortunately is not in the mount callback)
  • LiveSub hijacks the def update(%{id: id, init: true}) of the LiveComponent. Unfortunately I don’t have any other ways to make it work. You can choose not to override it and you can manually initiate LiveSub.

Most Liked

ouven

ouven

Hi,

many thanks for your library!

I was thinking about your “Drawback 2” with the hijacking of the update function and a pattern popped into my mind, that I have seen some time ago. You can generate an overridable update function and call super in it. So you’re users can still have their own update function:


  defmacro __using__(opts \\ []) do
    quote do
      @before_compile unquote(__MODULE__)
      ... more code
    end
  end

  defmacro __before_compile__(%Macro.Env{} = env) do
    [quoted_update(env)]
  end

  defp quoted_update(env) do
    if Module.defines?(env.module, {:update, 2}) do
      quote do
        defoverridable update: 2

        def update(assigns, socket) do
          .... <your code>
          super(assigns, socket)
        end
      end
    else
      quote do
        @impl Phoenix.LiveComponent
        def update(assigns, socket) do
          .... <your code>
        end
      end
    end
  end

I hope it severs you :slight_smile:

Where Next?

Popular in RFCs Top

laibulle
Hello, I am playing with quantitative finance with Elixir. This library is more a way for me to explore and learn in this area and especi...
New
dimamik
Hi! I’ve been working with Pipecat in Python for quite a while now, and while it’s effortless to set up, when building AI voice agents at...
New
WyvernoDragon
I’ve been working on pgRx: a query engine that uses BEAM lightweight processes instead of PostgreSQL’s fork-per-connection model. The nu...
New
Hedgehog-ai
Hello I would like feedback on an experimental neuroevolution (including substrate encoding) library called Bardo based on the amazing wo...
New
bluzky
Hi everyone, I would like to introduce my new project OrangeCMS, it’s an application that help you to create/edit content post for your ...
New
jarlah
Hi! I have recently created, after having tried to get in touch with the creator of excontainers for quite some time, a new library call...
New
sodapopcan
EDIT: I forgot to link the repo, ha: GitHub - sodapopcan/vials: Wrappers for mix tasks This project is not quite ready for prime-time bu...
New
manuel-rubio
There was some time when I started thinking about giving a boost to Lambdapad, the initiative from @garretsmith in Erlang that I loved wa...
New
KristerV
How I currently use Hexdocs I use hexdocs all day every day, but finding the right module and function takes too long even with my bookma...
New
BartOtten
This thread once discussed Routex in it’s early form. It has been repurposed to gather feedback and discusses pre-releases. Currently: p...
New

Other popular topics Top

mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I fore...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
hariharasudhan94
Lets say i have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; "XX...
New

We're in Beta

About us Mission Statement