View Source LiveModel (live_model v0.1.0)

Create and manage a LiveView model. A model allows you to define your LiveView assigns declaratively, similar to how you would use a Phoenix.Component and its attr/2,3 macro.

To create a model, import this module and use the defmodel macro, like so:

defmodule MyAppWeb.MyLive.Model do
  import LiveModel

  defmodel do
    field :user, MyApp.User.t(), required: true
    field :grocery_list, [MyApp.Food.t()], default: []
    field :sale_text, String.t() # defaults to `nil`
  end
end

Then, in your LiveView module:

defmodule MyAppWeb.MyLive do
  use MyAppWeb, :live_view

  alias MyAppWeb.MyLive.Model

  # un-import `assign/2,3` and friends that are included in
  # `use MyAppWeb, :live_view` to avoid accidental use.
  # (You should use the model helper functions instead, explained
  # later in this moduledoc)
  import Phoenix.Component, except: [assign: 2, assign: 3, assign_new: 3, update: 3]
  # alternatively, if you don't use other functions in
  # `Phoenix.Component` and you get an "unused import" warning
  # from the above, you can do something like this instead:
  # import Phoenix.Component, only: []

  @impl true
  def mount(_params, %{"user_id" => user_id}, socket) do
    user = MyApp.get_user!(user_id)

    {:ok, Model.assign_new(socket, user)} # assigns a new model struct under the `@model` assign
  end

  @impl true
  def handle_event("new_sale", _params, sockt) do
    # update assigns with `Model.put/2,3` and `Model.update/3`
    {:noreply, Model.put(socket, :sale_text, "Apples are now 10% off!")}
  end

  @impl true
  def handle_event("some_event", _params, sockt) do
    # Dialyzer will warn you when trying to put/update invalid keys
    {:noreply, Model.put(socket, :bad_key, :uhoh)}
  end

You would then access assigns in your render function/template via @model.assign, instead of @assign.

The defmodel macro will create a struct and t() type for you. It will also create the following helper functions:

  • new/x: creates a struct, where arity x is the number of required fields plus 1. Required fields are passed as individual arguments to new/x, and optional fields are passed in a Keyword list (or another Enumerable like a map)
  • assign_new/x: similar to new/x, but takes the socket as the first argument and assigns the new struct under @model.
  • put/2-3: given a LiveView socket, updates the :model assign with the given field(s) and value(s). This function is meant to replace use of Phoenix.Component.assign in your LiveView
  • update/2-3: given a LiveView socket, updates the :model assign by passing the current value under field to the given updater function. The result then replaces the original value. This function is meant to replace use of Phoenix.Component.update in your LiveView

Please read each function's documentation for more information.

Much of the implementation of defmodel is heavily inspired by Lucas San Román's typedstruct macro. You can read more about it (and Elixir's AST/macros in general) in their blogpost.

The "model" naming scheme is inspired by the Elm architecture/programming language.

Summary

Functions

Define a LiveView model.

Functions

Link to this macro

defmodel(list)

View Source (macro)
@spec defmodel(do_block :: list()) :: Macro.t()

Define a LiveView model.

This macro should be given a do block, whose contents are fields:

defmodel do
  field :my_string_assign, String.t(), default: ""
  field :my_number_assign, integer(), required: true
end

See the LiveModel documentation for more info and examples.