shankardevy

shankardevy

Currently when I assign a value in conn.assigns in the router, the assigned value is not merged with the socket.assigns.

Patching this line https://github.com/phoenixframework/phoenix_live_view/blob/v0.11.1/lib/phoenix_live_view/static.ex#L114 to include the assigns from conn works.

However, I do not understand if there is any reason for not doing this. Also I see the conn.assigns is copied to private.assign_new but don’t understand the purpose.

Anyone having background on this could explain the rationale?

Showing Posts 1 to 10

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Live view renders happen twice. There is the first static render that happens as part of an HTTP GET request. The conn is evaluated, a socket built / mounted, the page rendered. After that though the socket goes away entirely, it was just there to address the HTTP get request, there is no persistent connection.

Then the javascript on the page runs, connects via websocket, and does a second render. This time the socket sticks around because you’ve got a persistent connection. Notably though on this run, there is no @conn. In fact it’s a totally separate request, that could even happen to a totally different server from the GET request.

In the GET request, the assigns can be copied into the socket, because the socket is just basically exists to help you avoid writing totally different code for the static render. BUT on the second request, there’s no @conn for the values to be copied from at all. This is why the socket |> assign_new function takes an anonymous function arg. On the GET request assign_new will just use the assign from the @conn. But on the second live request, how can it get its value? It runs the anonymous function.

16
Post #1
shankardevy

shankardevy OP

Thanks @benwilson512 for the explanation on the socket life cycle. That was useful.

Does this mean, I cannot have a pipeline plug in my router that can assign a value and have it available in all my live view? basically this is what I am after.

In normal page requests, I can set for eg user_id in the session and load the user, assign it in the conn in my plug in the router pipeline. Setting it on the router level, helps not to do the same operation in multiple controllers.

However, in the case of LiveView, I can only set the session value in the pipeline plug and have it available in all LiveView pages but have to load assigns individually for every liveview individually. is there a solution for this?

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

This is basically correct. What I did in my app is create a %Context{} struct which holds things like the current user. Then I setup this context for both plug and in my live view:

In my plug:

    case Common.Sensetra.lookup_context(conn.host, org_id, token) do
      {:error, :no_grant} ->
        conn
        |> redirect(to: "/")
        |> halt

      {:ok, ctx} ->
        conn
        |> assign(:context, ctx)
        |> assign(:host, conn.host)
        |> merge_assigns(assigns_from_context(ctx))

      {:error, %{"errors" => _}} ->
        conn
        |> put_session(:token, nil)
        |> redirect(to: "/")
        |> halt()
    end

In my live view:

  # elsewhere
  import CommonWeb.Socket
  # then
  def mount(params, session, socket) do
    socket = init_assigns(params, session, socket)
defmodule CommonWeb.Socket do
  import Phoenix.LiveView

  @doc """
  This initializes the socket assigns
  """
  def init_assigns(params, session, %Phoenix.LiveView.Socket{} = socket) do
    %{
      "token" => token
    } = session

    socket
    |> assign_new(:token, fn -> token end)
    |> assign_new(:host, fn ->
      get_connect_params(socket)["host"] || raise "pass in the host!"
    end)
    |> setup_context(session, params)
  end

  defp setup_context(socket, _session, params) do
    socket =
      socket
      |> assign_new(:context, fn ->
        {:ok, ctx} =
          Common.Sensetra.lookup_context(
            socket.assigns.host,
            params["organization_id"],
            socket.assigns.token
          )

        ctx
      end)

    socket
    |> assign(CommonWeb.Plug.Application.assigns_from_context(socket.assigns.context))
  end
end

I use assign_new because I also call Common.Sensetra.lookup_context() inside of a plug. That plug will do things like redirect you to login if there is no current user. Since I look it up in the conn, I use assign_new so that in the socket part of the GET request it isn’t called twice.

You can mostly ignore the whole "host" thing I pass in. Our application does subdomain based theming so it’s important for us.

12
Post #3
shankardevy

shankardevy OP

Thanks @benwilson512 for these code examples. Much appreciated. If I am not mistaken, basically the following code is what is required to set assigns to the socket that are common and it has to be manually wired in every LiveView where the common assigns is required.

  In my live view:
  import CommonWeb.Socket
  
  def mount(params, session, socket) do
    socket = init_assigns(params, session, socket)

Do you think it calls for a feature like in plug macro in Phoenix Controllers?

@josevalim @chrismccord is it a good feature request to LiveView? Basically, as of now, there exists no way to set a common assigns shared by different LiveView pages like by setting them in pipeline plug (as in the case of normal http requests)

Could we have a LiveView Pipeline that takes in a socket and return an updated socket before it reaches the individual LiveView mount? Do you think this is an important addition? If so, I am interested to contribute.

Ideas that comes to me is a plug-like interface that takes in a socket and returns an updated socket.

In the MyApp’s endpoint.ex, we could have an additional preprocess key like:


  socket "/live", Phoenix.LiveView.Socket, 
    websocket: [connect_info: [
        session: @session_options,
        preprocess: MyApp.LiveViewPlug
      ]]

and we could use this config here: phoenix_live_view/lib/phoenix_live_view/socket.ex at main · phoenixframework/phoenix_live_view · GitHub to load assigns in all LiveView.

Or we could have Phoenix.Controller.Pipeline style plug so that each liveview can use a preprocessors to @socket like

plug GetCurrentUser

If there is a better approach to do this, let me know. As mentioned earlier, I am looking for a cleaner way to set assigns that are common across multiple LiveView pages.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Yeah I’ve thought about this too. It’s definitely sub optimal to need to remember to put that at the top of every live view I have. At the same time it’s also nice and explicit, and low magic.

ryanzidago

ryanzidago

In your example, where is the %Context{} struct saved?
In an ETS table, in a database?

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

It isn’t saved, it is constructed on each request from the conn assigns / session.

silverdr

silverdr

The documentation for assign_new/3 at:

says: “When a LiveView is mounted in a disconnected state, the Plug.Conn assigns will be available for reference via assign_new/3, allowing assigns to be shared for the initial HTTP request.”

Example only shows passing stuff through session though. How does one “reference Plug.Conn assigns” in such case?

josevalim

josevalim

Creator of Elixir

You don’t do it explicitly. The cache is transparent because you are not supposed to rely on connection assigns. You need to assume you need to get everything from scratch.

silverdr

silverdr

I might be a bit slow today.. but.. how do I get everything from scratch? Do I have to put everything I need into session, or?

Where Next? Top

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 94592 917
New
cblavier
Hey there, It’s been more than a year since we started using LiveView as our main UI library and building a whole library of UI componen...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
heathen
Quite interesting article Google brought me. Didn’t find any mentions about it here. What do you think in general? Would you use togethe...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
AstonJ
Since we have deprecated our Erlang sections (as we have dedicated Erlang Forums now) let’s add this thread for those who’d like to post ...
New
maennchen
:warning: Security advisory: Decimal DoS vulnerability A vulnerability has been published for decimal where very large exponents can cau...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

Latest on Elixir Forum

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews