joshp

joshp

I’m updating an app from 1.5 to 1.7 and changing to components/heex, and it’s led me to a question about what the Right Way™ to do something is… Here is the current state of things:

I have a component that generates SVG code representing a gauge. Think speedometer 0-60 kinda thing. This object is updated every 15 seconds with fresh data and redraws.

Currently the way I have things organized to have a module in AppWeb/views/gauge_arc.ex, which contains a struct, a type, and helper functions, including a “build_from_values()” function which takes the numerical values, and populates the remaining fields of the struct with values like “svg_bg_path_string” and “svg_fill_path_string” and “main_label_text”.

In my LiveView I fetch the data, create a new %GaugeArc{} with the basic values, pass that to GaugeArc.build_from_values() to fill in the computed values, and store the entire %GaugeArc{} into the assigns in a variable indicating what the gauge represents. (my page has several GaugeArcs on it)
The LiveView then calls a LiveComponent for each arc, passing in the correct struct from assigns, and the component pulls out the values it needs from the computed fields via @gaugeArc.svg_bg_path_string and the like.

All in all, in phx1.5 land, it’s actually been a pretty reasonable way to move a bunch of data into a liveview, from a code organization perspective.

My questions though:

  1. I feel like passing a 30-field struct into @assigns for a component is probably… not recommended? The final LiveComponent only uses 13 of them, 3 of which should never change for the life of the component. My assumption is that the correct way to do this in 1.7 would be to create a component with 13 individual attrs, but this still leaves the problem of how to handle getting that data into the assigns in the LiveView itself. 13 attrs each for 4 or more individual arcs. I could have mount/1 create @arc1_val1, @arc1_val2, … variables, but that seems inelegant. Is that the recommended solution for something like this, or was I on the right track with a custom struct to pass into @assigns?
  2. In 1.7 LiveComponent world, where is the preferred place for the code for these sorts of custom structs or helpers? They are firmly display-only, and generate HTML/SVG code, so I felt that they belonged in AppWeb, not over in App, but I’m not quite sure where to put them in the directory structure. Putting the struct/type/functions directly inside the app_web/components/gauge_arc.ex didn’t seem right. A “helpers” directory perhaps? Curious to see what others do for this.

It’s worth noting that this is a very low-impact project, it will never have more than maybe 10 concurrent users, so sacrificing some performance was never a problem. I’m just trying to think through the Right Way™ so that if I ever run into a bigger project with similar needs I’ve got some ideas, and a better understanding of how te work with change tracking rather than against it. :slight_smile:

Showing Posts 1 to 6

LostKobrakai

LostKobrakai

This should actually be fine, if the struct is not also large in terms of memory usage. The important bit for change tracking is more around how you access data out of assigns and also that you precompute everything, so template just pull out values directly from the assigns (even if nested) and not do computations. You seem to be doing that.

I personally am not a big fan of phoenix having gotten rid of the views folder, which to me is where this stuff belongs. Good thing: The folders used by generators are suggestions, not rules. Add whatever you think makes sense.

sodapopcan

sodapopcan

If a helper generates HTML there is no reason not to make it a component AFAIC. It can then live anywhere in your lib/my_app_web/components/ directory.

joshp

joshp OP

This should actually be fine, if the struct is not also large in terms of memory usage. The important bit for change tracking is more around how you access data out of assigns and also that you precompute everything, so template just pull out values directly from the assigns (even if nested) and not do computations. You seem to be doing that.

Okay, that’s what I was thinking. I wasn’t sure how much extra work change tracking was doing for those struct fields that aren’t actually getting used. (Is it doing a deep comparison on the struct, for example, or does it just see struct and assume it’s changed and toss the whole thing is an “changed”?)
It’s premature optimization in this case, but if it’s doing a deep comparison then having a 2nd struct that was just the necessary computed results might actually be a good idea.

I personally am not a big fan of phoenix having gotten rid of the views folder, which to me is where this stuff belongs. Good thing: The folders used by generators are suggestions, not rules. Add whatever you think makes sense.

Yeah, I generally would just create a folder “helpers” or “support” and put things like the struct definitions in there. I know it works, I was just wondering if there was really a standard place for this sort of stuff in the new components organizational structure. One does try to adhere to standards so other people can read the code. :sunglasses:

The problem is that the code used, the %GaugeArc{} struct and it’s functions and type, aren’t specific to the component itself. The LiveView (dashboard/index.ex) has to call them. Calling GaugeArcComponent.build_from_values() and then storing the result into assigns and then passing that result into <.live_component module={GaugeArcComponent}> seemed somehow wrong, which is why I’ve got the %GaugeArc{} broken out into its own module currently.

sodapopcan

sodapopcan

I certainly was not suggesting anything like that!!! I’m not 100% on what you are after but it seems you have created a struct specifically for converting other structs into a normalized struct that can then be passed to a live component. In that case, I may actually be suggesting something like that… :thinking: If that is the case, don’t be afraid to be a bit OO and instead of calling GaugeArcComponent.build_from_values() and passing that result to <.live_component ... />, just pass the original struct to <.live_component ... /> and convert each case inside the component itself (ie, call build_from_values(%Polymorphic{} = guage_arc). If I’m not completely misunderstanding your usecase, this is basically what views would have been doing in that a view provides specialized presentation logic for a specific slice of UI.

I may be way off base, though. Seeing some more concrete code would really help if you haven’t already figured something out!

I’m also curious as to your use-case for live components. Could they just be function components in this case or no?

joshp

joshp OP

That’s an interesting callout… I realized while answering your next question that I should probably look at moving as much of that logic as possible into the component. Something that got forgotten when I moved to the LiveComponent!

I’m building a dashboard that renders a number of widgets. Each widget could be on the page multiple times with different data, and may be on different pages. Each widget has several controls on it to allow it to display its data in different ways, (Metric/Imperial unit toggle for example.) the setting for which should only change the display for the widget the control was changed for, not for all widgets on the page.
To my understanding this basically screams “LiveComponent”. (especially considering that I have no resource constraint and no concerns about scaling.) However I’m more than happy to be wrong if there’s a Better Way™. :grin:

sodapopcan

sodapopcan

Naw, definitely a case for a live component! I asked because I wasn’t sure whether you really need to keep all your state in the LiveView itself or not. If you did then a live component might not be needed.

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
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
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
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
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
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

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews