Passing argument to LV template without assigns

The UC: I have two structures, each with its own lifecycle. The structure A is the larger one and is always fully rebuilt for each LV rendering cycle. The structure B is the configuration of the structure A, is small in size and is intended to be updated (not fully rebuilt) in each LV rendering cycle, with full reset on refresh. The structure B is a perfect candidate for the socket.assigns while the structure A is not for I don’t need it assigned.

However, both the rebuild of structure A instance and the updating of structure B instance take place within a single algo run. The problem that arises here is that I cannot call this algorithm from within the template as I don’t think (correct me if I am wrong) that there is a way to assign the updated structure B instance from the template. While if I run the algo from my LV module (in the handle_info function, for example) the only choice I have then is to assign both instances to socket.assigns for I don’t know how else I can make the structure A instance visible to the template.

In short, is there a point in the LiveView framework where I could compute the instances of both structures and assign just one of them, while making the other one also accessible to the template code, but without assigning it to the socket.assigns?

How about keeping A in an assign, but marking it with :temporary_assigns? Do you have a reason to keep it out of assigns besides memory usage?

Temporary assigns are also assigns, it’s just that they get reset to a predetermined value by the framework. The reason why I’d like to, if possible, pass it to the template some way other than via assigns is that I don’t need it assigned and I don’t need the liveview to bother with it as the assigned changes that matter (that trigger the liveview to rerender) are assigned as well so there’s no need for this structure to trigger it as well. Actually, the thing is that I don’t know what the expense of having structures assigned to LV socket with respect to the LV behavior is. The documentation is very opaque on the inner workings of the LV and I simply don’t have time to reverse engineer the code.

Are you constructing it in the def render callback?

Do note that if live view can determine that the assigns have not changed it will not re-render that part of the page, nor will it push updates over the wire.

Some of this feels like premature optimization.

Assigns are the only way to get a value in the template, you do need it to be assigned. I would start with the simple assign based solution, and then measure memory usage and benchmark various aspects of your live view. If you have memory savings you want to achieve, temporary assigns are the solution. If it isn’t clear how to apply those to your situation, that’s the line of questioning that is likely to be most productive.

1 Like

No. I do it in handle_info where I handle data model change messages (that I subscribe my LV module to). But it’s pretty much the same thing,

Correct me if I am wrong, but AFAIK the LiveView simply requires whatever to change in its assigns to rerender. And when re-rendering it’s sending the diffs resulting from your template code.

Thank you for pointing this out. Just needed this confirmation so I don’t waste time speculating. As I said, my structure A instance gets fully reconstructed based on other data (structure B and some other data) that get assigned anyway, so technically, another assign of structure A (which is a data transformation of the aforementioned) is redundant. The only reason why I end up needing it assigned is the point that you emphasized (being the only method to pass it to the template). Just wanted to make that clear. Thanks.

Btw, this is the second time so far I happen to need something like :ignored_assigns.

Live view has several optimizations to do better than this. For example if you render a partial, and none of the assigns passed to the partial have changed, the entire partial will not be re-rerendered. This may be true for things like if blocks too, I don’t remember off the top of my head.

Ah so, this might seem to be the same, but it is importantly very different. Render is called after every handle_ function returns, every time. If the only thing that ever happens is a specific handle_info clause then I guess they’ll run with basically the same frequency, but I do think the distinction matters.

Can you elaborate how this would work?

Look. For all I know, it takes a single non-related assign to get it to rerender everything that changed. For instance, if you have an input text change that you handle the blur event for and your business logic rejects the change for some reason, you can’t just change this specific state in your liveview structure for it remained as it was prior to the change and LV would not revert the content of the input text element to what it was before. That is unless, of course, you assign, say an unrelated temporary assign which will make it take into account the diffs between the dom tree and the liveview state and, effectively, revert the undesired change in the input field.

Of course.

It would simply denote the particular assign as an argument available to the template, that is should any rerendering be triggered otherwise, but should not consider the particular assign to trigger the rerendering.

To some extent this is just fundamentally at odds with the live view paradigm. What you’re suggesting is basically a side cause, a value that is not treated as an input to the render function, but rather a thing that simply happens to be available. This is fundamentally at odds with how LiveView (and similar UI frameworks) treat rendering, it is a pure relation between a set of inputs and an outputed template. This is, for example, why calling DateTime.utc_now() in your template or in a view helper will not work correctly, and why you should instead do |> assign(:now, DateTime.utc_now()) and use @now.

However! Perhaps the issue here is that you’re operating as if the socket assigns are the only way to maintain state on the socket. Maybe using the private key of the socket will help, so that you can hold on to a value and then only at some later time actually add it to the assigns to be rendered. You could place the value you get from handle_info in the socket private key, and then you can have some other event move the value from private to assigns when you want it to effect the view.

1 Like

Maybe, but you must admit that even you called for it implicitly several comments above:

I don’t do things like this for I don’t keep any logic whatsoever in my templates, but I’d like to know why you are suggesting it be wrong execution-flow -wise if at all. Or it’s about error/exception handling, suggesting the template should only read pre-computed data from structures (which too can be subject to runtime exceptions, so I don’t see the point if this is the argument).

Not my case. I don’t need it for later use, I just don’t know the expense for LV to handle each assign and by default, I tend to minimize the unnecessary operations, although this expense may be insignificant. As originally stated, calling the computation function directly from template would be ideal (unless there is something I am not aware of), for this is a completely redundant state to the one already assigned to the LV socket.

Btw, how do I access this private key if I want anything stored there? Manually - by modifying the structure directly? I am not aware of LV functions for that.

Also, if I store anything in this private key, is it like an assign or like a temporary assign in terms of the lifecycle?

It is like an assign, in that once placed is is there in memory, although it is not available in the template, nor will changing it trigger a re-render. Think ordinary genserver state.

Yes, it is a documented part of the structure, you can safely access it directly.

If I was unhappy about assigns being the only way to get data in, then sure, but I’m very happy that assigns are the only way to get data in. This provides all sorts of wonderful guarantees that make reasoning about views easier, and provides great hooks for automatic optimizations that obviate the need to try to create complicated ones like you are doing.

It’s wrong because the moment you have side causes rendering isn’t pure. If rendering isn’t pure, it makes the question “when to render” a vastly more complex one that you suddenly have to manage. LiveView’s paradigm, much like react and other similar paradigms is: Update the state => render. If the state updates, the output from render will reflect that state. This is an amazing guarantee, and it makes reasoning about live view incredibly easy. If you introduce side causes, changing state is no longer enough to know that you need to trigger a re-render. Your ability to reason in a linear path about the state of the live view process and the content of the template goes out the window.

Pure render functions improve performance as well. You seemed to simply not believe me before, but LiveView really does have the assign diffing optimizations, and with pure rendering it can also safely increase those optimizations as time goes on. I know for sure it works with live components and render calls, if you didn’t see it in your use case I’m happy to dig into the specifics with actual code. If render functions aren’t pure though, the ability to get automatic optimizations via assign diff tracking also goes out the window, because we no longer know that we can derive a template from the assigns.

Ok. So in short, your point being: If your template depends on it, have it assigned even if you don’t see any reason for it (e.g. being only a transformation of other data that is already assigned), in order not to worry of whatever discrepancy / side effects that may pop up in the future.

The absence of code examples has me a little bit lost here. Let’s make this slightly more concrete. You have @foo, and there is some transform(@foo) that produces a bunch of data you actually want to render, but transform/1 is somewhat expensive yes? So you want to make sure that it only actually gets called if @foo changes, and not simply because some other value @bar changes?

Eg:

# index.html.leex
<html>
<body>
<p><%= @bar %></p>
<p><%= transform(@foo) %></p>
</body>
</html>

If @bar changes a lot, but @foo changes very rarely, you don’t want to re-run transform/1 every time @bar changes.

The first thing I’d do is make sure that this problem actually happens, live view is getting new optimizations all the time, I’d put some kind of debug logging inside transform/1 and validate that it is being in fact called every time @bar changes.

If it is, try moving transform(@foo) into a partial (make sure the partial is .leex):

# _foo.html.leex
<p><%= transform(@foo) %></p>

# index.html.leex
<html>
<body>
<p><%= @bar %></p>
<p><%= render(@live_view_module, "_foo.html.leex", foo: @foo) %></p>
</body>
</html>

This allows optimizations around rendering partials to take place. If that still isn’t working then that sounds like a bug, those optimizations have been in for a while. If you have a BUNCH of state you want to manage around @foo a component may help, but this should help.

You can create some |> assign(:baz, transform(assigns.foo)), and then use @baz in the template, which helps avoid having transform/1 called unnecessarily, but at the expense of having to keep the result of transform/1 in memory. If this is an issue we can explore the temporary assigns route, but without a good illustration of the issue it’s hard to talk about specific tactics. It depends a lot on exactly how expensive transform/1 actually is.

2 Likes

Yes, that is precisely what I am doing now. I was just asking if there was a way not to. And transform/1 is expensive enough for not having to invoke it more than once per cycle.

But, now that you’ve mentioned this private key thing, I’m considering changing the design a bit so that the result of transform/1 keeps gettting assigned but its input data does not (but stored as private instead). Need to check of whether that’s gonna be possible. Thanks!

1 Like

No problem, hope it helps! I’d also definitely check out whether using a partial solves your issue without any extra fuss.

Can you elaborate on what you mean by cycle here? Do you have some kind of timer or something that updates a value every X seconds? Or is this more like you’ve got a form on the page with phx-validate and you don’t want key strokes to cause transform/1 to run?

The result of transform/1 is a tuple of two structures (A & B) so the above example with partials won’t do. Besides, there’s is always the problem I originally mentioned. The structure B is getting modified by the transform/1 function (it is also one of the inputs) and it therefore must be assigned to live on until the next page/LV refresh (unlike structure A).

Rendering cycle. Not related to any timer, it’s just that the structure A is rebuilt on each re-render (re-render being triggered by change of the underlying structure that transform/1 takes for input in addition to structure B).

Just to make sure we’re on the same page, if there data isn’t changed, no re-render occurs. There isn’t some continuous re-rendering cycle that’s happening all the time that would cause transform to use a lot of CPU.

I’m not really following, can you provide a concrete example? It can still be a sketch, but I’m having trouble following the relationships between the values here.

The app is intended for use by really many users simultaneously. This particular part can be quite costly as events are fired on even the slightest changes to the model, which again, triggers re-rendering and transform/1 along with it.

{ a, b} = transform( c, b)

  • a is obviously constructed from the scratch on each re-render and is moderately large (in terms of data contained, it can be thought of as a subset of c but a transformed one)
  • b is very small, is updated and must therefore be kept assigned to socket.assigns
  • c is potentially large and is getting updated by the server-side API functions and persisted into the DB
  • both a and b are read from by the template

Ah. So this is important, I thought transform was pure. If transform isn’t pure then definitely call it inside the live view module and create assigns for a and the updated b.

I am already doing it for I have no other choice. As noted before, I intend to check if c can be put in private key instead of assigning it for a and c are data-redundant from the template perspective.