Accessing keys with dashes in HEEx templates

With HEEx and components using dashes in attribute names is pretty easy now:

<.component some-attr="value" />

What’s not clear to me is… how do I actually access those in a template since @some-attr doesn’t work?

You should be able to access it via assigns.


Map.get(assigns, :"some-attr")

I’ve talked to @chrismccord about that on slack a few weeks ago. Given html attributes with _ are valid attributes there’s no good way to “normalize” between dashes and underscores. I’ve gone with only using _ for all attributes on function components, which don’t go directly onto a html node.

You can abuse this trick for other style keys too.

In one project I use Map.get(assigns, :"+class") to differentiate between <.comp +class="merge onto default classes"/> and <.comp class="replace default classes"/>.

Not sure I’ll carry the style forward to other projects because it’s kinda odd syntax and feels like it might suddenly become verboten in a future release but there you go.

class can receive a list as value, so I usually compose input + static classes like that class={[@class, "static_classes"]} + assign_new(assigns, :class, fn -> nil end)

That’s not quite the same thing though is it? Where you can elect to replace default classes wholesale or provide additional (or omit modifications entirely).

The use case here was having icons that:

  • render as normal (10px high in text-color) <Icon.hand/>
  • render as normal with some modification (10px high with animation) <Icon.hand +class="red animate-wave"/>
  • render abnormally in rare cases (20px high with underline) <Icon.hand class="h-20px underline"/>

As given you’d always include static_classes no?

Basically +class was just a cheeky way of writing extra-classes which I think I actually had initially but got sick of typing out :slight_smile:. The + is pretty concise notation but I think it opens the door to “well, why don’t we have -class to remove classes?” and at that point your writing a bad API to all your components, which is probably why the idea makes me feel a bit uncomfortable.

If you need that I’d say your default and/or api is bad. At this point I’d probably go with a height attribute (default to 10), which maps to the relevant class, which is again applied as part of the list on class={…} within the component.

I had actually read something similar on Github related to a feature to normalize dashes and underscores and José had ultimately responded similarly. They’re both valid.

In this case, I want to be able to have one component optionally override some phx- bindings.

After looking at the responses, I guess a better question is this:

Will using functions to access keys in assigns work the same as @ in terms of the templates knowing what they’re supposed to in terms of updating properly as changes happen?

In the example:

Map.get(assigns, :"some-attr")

It’s not quite the same as using @ since a missing key would result in nil. Which raises another question, is there anything “wrong” with accessing assigns values in a template via either Map.get/2 or [], especially if you want to check for a conditional value rather than preload a default on the assigns?

Yes. In templates only @… will allow change tracking on the value.

So there’s no way to actually deal with change tracking if an attribute has a dash?

(This is fine for my use case, but there isn’t a macro or something that can be used in place of @?)

@ already is the macro.

Right. But it’s a type of macro that can’t deal with a key with dashes. @ is limited here. I was wondering if there was a less pretty alternative that could be used with assigns where the keys are dashes.

The problem here is this:

Variables in Elixir must start with an underscore or a Unicode letter that is not in uppercase or titlecase. The variable may continue using a sequence of Unicode letters, numbers, and underscores. Variables may end in ? or !. See Unicode syntax for a formal specification.

Elixir’s naming conventions recommend variables to be in snake_case format.

EEx parses @… as @ followed by a variable name. Dashes are not allowed in variable names.

1 Like

I understand. It seems the reality is if you want to handle change tracking in a template, there’s no way to do it using an assigns with a dash and names with dashes should be confined to certain things.

Does assigns_to_attributes/2 help solve your problem at all?

I’m not actually having a problem here. It was more of trying to understand how to work with dashes and the answer seems to be if you need change tracking on an attribute, don’t use a dashed name. (Your only other real option is to assign it to a non-dashed name.)

And it’s not even just dashes per se, the real issue is that an assigns is a map and it can take key names that are incompatible with variable names. So any key name that’s not a valid variable name presents some challenges because they can’t be accessed or referenced in quite the same manner in templates.

In my case, I wanted to do something with dynamic phx- bindings so I could pass them to a component and override defaults.

While I didn’t use assigns_to_attributes/2, I wrote a similar function that filtered certain assigns into attributes and combined it with a splat.

You could stuff those values into yet-another-assigns (like in your example where the author uses @extra).

I wasn’t so much trying to solve a specific problem here so much as trying to understand dashes in assigns names because, with the function components, this seems to be a more widely used convention and, given the behavior of content tags using phx_click versus the form components uses phx-click there are cases where this is confusing and, in my opinion, not documented well enough.

I would be happy to actually update the documents on this matter, but I didn’t feel I understood the realities well enough to do so. (I think I could give it a try now though.)

1 Like