Passing Unknown Attributes into Your Component

Fly.io runs apps close to users, by transmuting Docker containers into micro-VMs that run on our own hardware around the world. This is a recipe about making your stateless components in LiveView more reusable and generic. If you are interested in deploying your own Elixir project, the easiest way to learn more is to try it out; you can be up and running in just minutes.

On your LiveView page, you are using a custom component. You want to be able to pass HTML attributes into the component, but the component doesn't know anything about the attributes being passed! You need a way to pass arbitrary attributes through and get them where you want them.

Problem

You're getting into LiveView and starting to create more components to reuse in your application. Now you're having a problem creating components and passing in common phx-click or phx-value-myvalue attributes and getting them where you want in your component's markup.

Example:

You want to use the component like this:

<.special_button title="Yes, sign me up!"
  phx-click="select-sign"
  phx-value-elect="yes" />

The component's markup generates something like this:

<div class="container-classes">
  <div class="my-button-classes" I WANT THE CLICK SETTINGS HERE>
    Yes, sign me up!
  </div>
</div>

Notice the big "I WANT THE CLICK SETTINGS HERE"? That's where I want the phx-click and phx-value-elect attributes to go. But the component is supposed to be reusable! Somewhere else it's going to be a different phx-value-other attribute.

How do I get the phx-click, phx-value-* and maybe even phx-target attributes through to the component and control where they go?

Solution

In LiveView 0.16, the function assigns_to_attributes/2 was added. We can use this to help solve this problem!

Here's why this function is helpful:

Useful for transforming caller assigns into dynamic attributes while stripping reserved keys from the result.

If we call assigns_to_attributes with the assigns example from the problem above, this is returned:

assigns_to_attributes(assigns)
#=> ["title": "Yes, sign me up!",
#=>  "phx-click": "select-sign",
#=>  "phx-value-elect": "yes"]

The assigns_to_attributes function takes a 2nd argument that comes in handy now. It let's us exclude assigns from becoming attributes. Since we're using :title internally, we want to exclude it.

This is what it looks like when we exclude :title.

assigns_to_attributes(assigns, [:title])
#=> ["phx-click": "select-sign",
#=>  "phx-value-elect": "yes"]

Now that we've removed all the assigns that the component is using, we want to take the rest and just splat them where we want them but as HTML attributes.

How do we do that?

Let's say our simple component looks like this:

def special_button(assigns) do
  extra = assigns_to_attributes(assigns, [:title])

  assigns =
    assigns
    |> assign(:extra, extra)

  ~H"""
  <div class="container-classes">
    <div class="my-button-classes" {@extra}>

    </div>
  </div>
  """
end

First, we get extra to hold the attributes we want to render.

Next, we add :extra to the assigns so our HEEx (~H) template can use it.

Finally, the {@extra} takes all the attributes not excluded and outputs them as HTML attributes on the div we want! Our rendered markup ends up looking like this:

<div class="container-classes">
  <div class="my-button-classes"
        phx-click="select-sign"
        phx-value-elect="yes">
    Yes, sign me up!
  </div>
</div>

Nice! That's exactly what we were aiming for!

Discussion

The assigns_to_attributes/2 function is a good tool for making reusable LiveView components. It also gives us control over where common attributes like phx-click and phx-value-* get rendered.

This means our components can be more generic and reusable!

Fly ❤️ Elixir

Fly is an awesome place to run your Elixir apps. It's really easy to get started. You can be running in minutes.

Deploy your Elixir app today!