Phoenix LiveView cho người mới bắt đầu

Bài hướng dẫn này cần bạn đã cài đặt ElixirPhoenix trước đó

Nếu bạn chưa biết LiveView là gì, thì thực ra là một wrapper bao gồm websocket cho phép truyền dữ liệu và update lại page. Tôi khuyên bạn nên xem bài nói của Chris McCord ở ElixirConf EU 2019 để hiểu hơn.

Initial Setup

Đầu tiên bạn cần tạo một Phoenix Project

$ mix phx.new random_cat --no-ecto

Trả lời Y khi được hỏi Fetch and install dependencies?. Ta đang truyền --no-ecto vì ở đây ta sẽ không thao tác với database.

Để kiểm tra có đang hoạt động không, khởi chạy server và mở trình duyệt lên và vào địa chỉ http://localhost:4000. Bạn nên nhìn thấy message 'Welcome to Phoenix!' cùng với vài đường link.

$ cd random_cat
$ mix phx.server

Trang này được điều khiển từ hàm index nằm trong lib/controllers/page_controller.ex. Giờ thì ta cần cài đặt thêm LiveView. Để làm vậy, mở file mix.exs và thêm {:phoenix_live_view, "~> 0.3.1"} vào deps, cũng như thêm cả httpoison. Đây là một thư viện HTTP client, cần thiết cho project này:

defp deps do
[
  {:phoenix, "~> 1.4.9"},
  {:phoenix_pubsub, "~> 1.1"},
  {:phoenix_html, "~> 2.11"},
  {:phoenix_live_view, "~> 0.3.1"},
  {:phoenix_live_reload, "~> 1.2", only: :dev},
  {:httpoison, "~> 1.6.1"},
  {:gettext, "~> 0.11"},
  {:jason, "~> 1.0"},
  {:plug_cowboy, "~> 2.0"}
]
end

Khởi động lại server và thêm các thư viện bằng lệnh mix deps.get. Sau đó khởi động lại server bằng mix phx.server.

LiveView

Đây là phần thú vị. Đầu tiên định nghĩa /live endpoint. Mở lib/random_cat_web/endpoint.ex và thêm socket "/live", Phoenix.LiveView.Socket:

defmodule RandomCatWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :random_cat

  socket "/live", Phoenix.LiveView.Socket
  ...
end

Ta cần thêm signing salt, mở config/config.exs, thêm live_view: [signing_salt: ...]

config :random_cat, RandomCatWeb.Endpoint,
  url: [host: "localhost"],
  secret_key_base: "abc",
  render_errors: [view: RandomCatWeb.ErrorView, accepts: ~w(html json)],
  pubsub: [name: RandomCat.PubSub, adapter: Phoenix.PubSub.PG2],
  live_view: [signing_salt: "xyz"]

Bạn có thể sinh salt bằng mix phx.gen.secret 32.

Tiếp theo bạn cần khởi tạo socket connection qua javascript. Thêm phoenix_live_view vào npm dependencies:

{
  "dependencies": {
    "phoenix": "file:../../../deps/phoenix",
    "phoenix_html": "file:../../../deps/phoenix_html",
    "phoenix_live_view": "file:../../../deps/phoenix_live_view"
  }
}

Và chạy npm install --prefix assets. Giờ thì mở assets/js/app.js và thêm:

import {Socket} from "phoenix"
import LiveSocket from "phoenix_live_view"

let liveSocket = new LiveSocket("/live", Socket)
liveSocket.connect()

Đến đây là tất cả Javascript ta cần viết.

Giờ thì ta tạo LiveView. Tạo một file bên trong lib/ranom_cat_web/live (bạn cần tạo thư mục live trước), tên là cat_live.ex, với nội dung:

defmodule RandomCatWeb.CatLive do
  use Phoenix.LiveView


  def render(assigns) do
    ~L"""
    <img src="<%= @url %>">
    """
  end

  def mount(%{}, socket) do
    fallback = "http://lorempixel.com/g/500/300/animals/"
    case HTTPoison.get("http://aws.random.cat/meow") do
      {:ok, %HTTPoison.Response{body: body}} ->
        case Jason.decode(body) do
          {:ok, %{"file" => url}} ->
            {:ok, assign(socket, :url, url)}
          {:error, _} ->
            {:ok, assign(socket, :url, fallback)}
        end

      {:error, _} ->
        {:ok, assign(socket, :url, fallback)}
    end
  end
end

Giờ thì ta sẽ phân tích từng đoạn. Cơ bản LiveView cần 2 hàm để hoạt động, rendermount. Đâu tiên là hàm render.

def render(assigns) do
  ~L"""
  <img src="<%= @url %>">
  """
end

Đoạn này định nghĩa làm các nào bạn render ra nội dung. Ở đây có thể là toàn bộ trang, hoặc chỉ là một phần. Bạn cũng có thể tạo ra một template. Phần -L gọi là một sigil cho live view template.

def mount(%{}, socket) do
  fallback = "http://lorempixel.com/g/500/300/animals/"
  case HTTPoison.get("http://aws.random.cat/meow") do
    {:ok, %HTTPoison.Response{body: body}} ->
      case Jason.decode(body) do
        {:ok, %{"file" => url}} ->
          {:ok, assign(socket, :url, url)}
        {:error, _} ->
          {:ok, assign(socket, :url, fallback)}
      end

    {:error, _} ->
      {:ok, assign(socket, :url, fallback)}
  end
end

Tiếp theo là hàm mount. Mount được dùng đến khi LiveView được render lần đầu tiên. Ở đây khá đơn giản, hàm này đang lấy data từ http://aws.random.cat/meow, fallback khi có invalid data, hoặc không fetch được. Nếu fetch data thành công thì ta hiện ảnh mèo.

Cuối cung ta cần thêm LiveView vào router. Mở lib/random_cat_web/router.ex, và thêm live "/cat", CatLive vào scope "/":

scope "/", RandomCatWeb do
  pipe_through :browser

  get "/", PageController, :index
  live "/cat", CatLive
end

Giờ thì mọi thứ đã ok, khởi động lại server và mở đường dẫn http://localhost:4000/cat. Ảnh mèo sẽ được hiện ra.

Giờ thì các duy nhất để update lại ảnh là refresh lại trang. Giờ thì ta sẽ thay đổi bằng cách thêm một button để update lại ảnh mà không cần refresh lại trang.

LiveView Event Handling

Ở đây event đang được handled khi bạn gọi một hàm từ trang, và đang được thực hiện qua socket, tuy nhiên bạn không cần quan tâm về điều đó cho lắm. Giờ ta cần bind event vào element và LiveView xử lý.

Đầu tiên ta cần update lại template. Ta sẽ thêm một button để trigger hành động trên server, thông qua phx-click binding. Hàm render của ta sẽ như sau:

def render(assigns) do
  ~L"""
  <div>
    <img src="<%= @url %>">
  </div>
  <button phx-click="moar">moar!</button>
  """
end

Giờ ta cần tạo một function để handle event click. Ở đây ta muốn cập nhật lại ảnh khi button được click. Do đó ta sẽ sửa lại function mount, để có thể tái sử dụng HTTP call. Hàm mount sẽ trông như sau:

def mount(_session, socket) do
  {:ok, assign(socket, :url, get_cat_url())}
end

Còn hàm get_cat_url sẽ trả về cho ta url:

defp get_cat_url() do
  fallback = "http://lorempixel.com/g/500/300/animals/"
  case HTTPoison.get("http://aws.random.cat/meow") do
    {:ok, %HTTPoison.Response{body: body}} ->
      case Jason.decode(body) do
        {:ok, %{"file" => url}} ->
          url
        {:error, _} ->
          fallback
      end

    {:error, _} ->
      fallback
  end
end

Cuối cùng, để handle moar event, ta gán :url đến url mới từ get_cat_url bất cứ khi nào hàm này được gọi.

def handle_event("moar", _values, socket) do
  {:noreply, assign(socket, :url, get_cat_url())}
end

Cuối cùng file cat_live.ex sẽ trông như sau:

defmodule RandomCatWeb.CatLive do
  use Phoenix.LiveView


  def render(assigns) do
    ~L"""
    <div>
      <img src="<%= @url %>">
    </div>
    <button phx-click="moar">moar!</button>
    """
  end

  def mount(_session, socket) do
    {:ok, assign(socket, :url, get_cat_url())}
  end

  def handle_event("moar", _values, socket) do
    {:noreply, assign(socket, :url, get_cat_url())}
  end

  defp get_cat_url() do
    fallback = "http://lorempixel.com/g/500/300/animals/"
    case HTTPoison.get("http://aws.random.cat/meow") do
      {:ok, %HTTPoison.Response{body: body}} ->
        case Jason.decode(body) do
          {:ok, %{"file" => url}} ->
            url
          {:error, _} ->
            fallback
        end

      {:error, _} ->
        fallback
    end
  end
end

Bài viết được dịch từ: https://kriwil.com/development/phoenix-live-view-beginner-guide/