Phoenix LiveView 0.18: New Special HTML Attributes
Tags: elixir
Published: 2022-10-18 11:39:24

Phoenix LiveView 0.18 just shipped, with lots of new goodies to make developing LiveView an even better experience.

In this post, I'll take you through a lesser-known new feature - LiveView's new special HTML attributes - and show you how to write cleaner HTML with `:if`, `:for`, and `:let`.

When we're done, you'll have an eloquent, ergonomic, and dynamic function component you can use to render a list anywhere in your LiveView app.

Let's dive in!

## What are Special HTML Attributes in LiveView 0.18?

LiveView special HTML attributes are inspired by a Surface feature called ["directives"](https://surface-ui.org/template_syntax#directives). Directives are built-in attributes that modify the translated code of a tag or component at compile time.

Directives are just one of a few new features, like component [slots](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#module-slots) and [declarative assigns](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#module-attributes), that LiveView has drawn from Surface. Surface is LiveView's independently developed, open-source component library, and it has often driven LiveView framework development forward by introducing (and battle-testing) new concepts quickly.

The `:if` and `:for` special attributes provide syntactic sugar for the `<%= if ... do %>` and `<%= for ... do %>` EEx calls you would normally expect to write in your templates.

The `:let` attribute works a little bit differently. It is used in component slots when you want to yield a variable from within the function component back up to the caller. You'll see this most often when you use the `.form/1` function component, but you can also use it to make your own function components even more dynamic.

Let's take a look at how you can use these attributes to write eloquent, ergonomic HTML in your LiveView templates. Along the way, you'll combine the usage of all three of these attributes to build a clean and dynamic LiveView function component.

## Cleaner LiveView Templates with `:if` and `:for`

You can use these two attributes in regular Phoenix templates, in components, and in slots to write cleaner HTML code. We'll take a look at an example using the `:if` directive first:

```erb
<div class="alert alert-danger" :if={@error_message}>
  <p><%= @error_message %></p>
</div>
```

The div with the `alert alert-danger` class that contains the error message markup will only render _if_ the `@error_message` is present and evaluates to `true`.

This approach replaces the more verbose one here:

```erb
<%= if @error_message do %>
<div class="alert alert-danger">
  <p><%= @error_message %></p>
</div>
<% end %>
```

The code that uses the `:if` attribute is easier to read--more eloquent--and easier to write--more ergonomic.

You can also use the `:if` attribute to conditionally render a function component, all in one simple line of code. Let's say we have a function component, `.error/1`, that wraps up the error message markup above. We can conditionally render it like this:

```erb
<.error message={@error_message} :if={@error_message}>
```

Now, let's take a look at an example that uses the `:for` attribute. The `:for` attribute can be used to iteratively render some content for each member in a collection, like this:

```erb
<tbody id="books">
  <tr :for={book <- @books}>
    <td><%= book.title %></td>
  </tr>
</tbody>
```

Thanks to the `:for` attribute, you don't have to establish your `for` loop explicitly within EEx tags, like this:

```erb
<tbody id="books">
  <%= for book <- @books %>
  <tr>
    <td><%= book.title %></td>
  </tr>
  <% end %>
</tbody>
```

Once again, we're left with code that is both more eloquent and more ergonomic. You can even combine the usage of `:if` and `:for` if you need to. Let's say we have a list of books to render, but we only want to display a given book if it is available in our online store. We can achieve that like this:

```erb
<ul>
  <li :for={book <- @books} :if={book.available}><%= book.title %></li>
</ul>
```

Since both `:if` and `:for` can be used in either plain HTML or in LiveView function components, we can wrap our list item HTML markup in a function component. Given the following function component:

```elixir
def list_item(assigns) do
  ~H"""
  <li>
    <%= @book.title %>
  </li>
  """
end
```

We can call on it like this:

```erb
<ul>
  <.list_item :for={book <- @books} :if={book.available} />
</ul>
```

This is a nice way to start compartmentalizing our markup into reusable function components made even cleaner with special HTML attributes.

We can do better with our function component, though. Right now, we've only encapsulated the markup for list items, not the entire list. On top of that, our `.list_item/1` function component isn't very reusable--it expects to be called with an assigns that contains an `@book` attribute set to something that responds to `.title`.

We'll build a more dynamic function component that uses component slots and the `:let` attribute to encapsulate the _entire_ unordered list into one dynamic component. When we're done, you'll have a dynamic function component that uses `:for` and `:let` to render any collection, anywhere in your LiveView app.

For this next example, we'll simplify our logic a bit by dropping the `:if` attribute and the requirement that a book should only render if it's available. We'll just focus on using `:let` and `:for`. Let's get started.

## Dynamic Function Components with `:let`

First up, let's use a named component slot to create a function component that renders an unordered list and its list items. Start by defining a top-level `.unordered_list/1` function component that renders a named slot, `:list_item`, inside the appropriate markup:

```elixir
def unordered_list(assigns) do
  ~H"""
  <ul>
    <li>
      <%= render_slot(@list_item, ...) %>
    </li>
  </ul>
  """
end
```

We're on track to render our unordered list component like this:

```elixir
<.unordered_list>
  # ...
</unordered_list>
```

When we call on the component, we'll set an assigns - `items` - equal to the list of books. Now, we can use `:for` in the function component HEEx to render a list item slot for each item in the `@items` assigns:

```elixir
def unordered_list(assigns) do
  ~H"""
  <ul :for={item <- @items}>
    <li>
      <%= render_slot(@list_item, item) %>
    </li>
  </ul>
  """
end
```

Putting it all together, we can call on our function component like this:

```elixir
<.unordered_list items={@books}>
  <:list_item :let={book}>
    <%= book.title>
  </:list_item>
</.unordered_list>
```

Here's where the `:let` special attribute comes in. In our function component, we are using `:for` to iterate over the list of books in the `@items` assignment. For each book in the list, bound to the `item` variable at each step in the iteration, we are calling `render_slot/2` with a second argument of `item`.

The call to `:let={book}` when we call on our `:list_item` slot sets a variable - `book` - equal to the second argument passed into `render_slot/2`. In this way, you yield variables from your function components back to the caller with the `:let` attribute. So, where we call `:list_item` when rendering the function component, we can operate on the `book` variable to display its title.

By combining `:for` and `:let`, we end up with an eloquent, ergonomic, and highly dynamic function component that we can use again and again in our application to render any collection into an unordered list.

Before we wrap up, there's one limitation that I'd like to point out here. Bringing back our "only render a book title if the book is available" logic is a little tricky here. You might want to do something like this:

```elixir
<.unordered_list items={@books}>
  <:list_item :let={book} :if={book.available}>
    <%= book.title>
  </:list_item>
</.unordered_list>
```

This is not possible, however. The component slot call cannot combine `:let` with `:if` in that manner. We can't take advantage of the `:if` attribute here and instead have to write something like this:

```elixir
def unordered_list(assigns) do
  ~H"""
  <ul :for={item <- @items}>
    <%= if item.available %>
      <li>
        <%= render_slot(@list_item, item) %>
      </li>
    <% end %>
  </ul>
  """
end
```

The downside is that the component becomes aware of the need to call `.available` on the item, making it less reusable.

## Wrap Up

LiveView is being adopted so quickly in the Elixir community (and beyond) partly because of its gentle learning curve and emphasis on developer happiness.

LiveView's new special HTML attributes make writing LiveView templates even easier and more fun. By borrowing ergonomic features from Surface, we end up with code that is painless to write (fewer onerous EEx tags!) and easy to understand and maintain.

Reach for these special HTML attributes when writing plain HEEx templates, LiveView HEEx templates, or function components. You'll end up with beautiful code.

Until next time, happy coding!

**P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, [subscribe to our Elixir Alchemy newsletter and never miss a single post](/elixir-alchemy)!**