▶hlongvu

Passing a Function Inside @Assigns in Phoenix - Elixir

As you know, the @assigns map of connection in Phoenix is the data map of variables to be rendering in view templates. We often use this conn.assigns to store our value. But how to store a function inside this?

I am encountering this problem when trying to create a template view for a list of products. This is a table with multiple columns, showing product details, but the last column will show action to do with that row. This action can be delete, edit or add to cart, and could be empty.

The problem is that I don’t want this action column to be a static menu(always comes with 3 options). I want this product list to display action base on the page I am visiting. In this page, the action could be edit, another page, action would be empty,… etc That would be great for reusing the template list and customize action whatever way I want.

My product_list.html.eex could be like this:

<%= if assigns[: product_list] do %>
<table class="table">
    <thead>
      <tr>       
        <th>Name</th>
        <th>Price</th>
        <%= if assigns[:delete_action] != nil do %>
            <th>Action</th>
        <% end %>
      </tr>
    </thead>
  <tbody>

  <%= for product <- @product_list do %>
      <tr>
        <td> <%= product.name%> </td>
        <td> <%= product.price%> </td>
       <%= if assigns[:delete_action] != nil  do %>
             <td">
                 <%= @delete_action.(product.id) %>
             </td>
       <% end %>
      </tr>
  <% end %>
  </tbody>
    
</table>

<% end %>

Look at this line: @delete_action.(product.id), this is how we call a func with params in Elixir.

Then by passing a delete__action into @assign, we could customize this action column whatever way we want. We will be reusing this template in another eex file as below:

<%= render MyWeb.ProductListView, "product_list.html", product_list: @list, delete_action: render_delete_action  %>

At first, I create a simple delete_action, show a link to post a delete request to product.id:

 def render_delete_action(id) do
    link("×", to: "/product/#{id}", data: [confirm: "Remove this product from list?"], method: :delete)
  end

But this will result in a compile error. How did that happen? It’s because when we pass our function in delete__action: render_delete_acton, Phoenix will evaluate that function immediately and delete__action will hold the result to that function instead of a func. Then in the template, the call to @delete_action.(product.id) will be invalid.

So how do we pass a function into @assigns?

It’s pretty simple, the function must return a func instead of data.

 def render_delete_action do
     fn id ->
            link("×", to: "/product/#{id}", data: [confirm: "Remove this product from list?"], method: :delete)
    end
  end

And our template should work as expected.

A nother way to do this is specify a function inside template call instead:

<%= render MyWeb.ProductListView, "product_list.html", product_list: @list, delete_action: fn x -> render_delete_acton(x) end  %>