Correct use of port subscriptions in a submodule reused multiple times?

464 views
Skip to first unread message

Matthieu Pizenberg

unread,
Nov 8, 2016, 12:19:37 PM11/8/16
to Elm Discuss
Hi, I am currently struggling with port subscriptions in a submodule (`Part`)
reused multiple times in a bigger (`Group`) module like:

-- In Group.elm
type
alias Model =
 
{ parts : Array Part.Model }

I've been using the following "batching" technique (here are the relevant parts of 3 files: Part.elm, Ports.elm, Group.elm)

-- Part.elm

type
Msg
 
= PortMsg (Some, Values)

update msg model
=
 
case msg of
   
PortMsg (some, values) -> (...) -- using Debug.log here to see how many times this is called

subscriptions
: Model -> Sub Msg
subscriptions model
=
   
Ports.portFunction PortMsg


-- Ports.elm

port portFunction
: ((Some, Values) -> msg) -> Sub msg


-- Group.elm

type
alias Model =
 
{ parts : Array Part.Model }

type
Msg
 
= PartMsg Int Part.Msg

subscriptions
: Model -> Sub Msg
subscriptions model
=
Sub.batch
 
<| Array.toList <| Array.indexedMap (Sub.map << PartMsg) (Array.map Part.subscriptions model.parts)


The problem I face is that when a Sub Msg is processed, the updates of all the parts are triggered instead of just the only one
that should be subscribing to the msg (I verified it with the `Debug.log` mentionned in the code).
This means that if I have 10 parts in the group, it will trigger the update function of the right part and also of the 9 other parts that should not be updated with this sub msg.
This is a huge efficiency problem in addition to potentially modifying my models incorrectly if I don't care somehow in the update function.

I fear I have come to this issue by design flaws since I'm not finding a way to overcome it.
Does anyone have an idea of what I'm doing wrong ?

OvermindDL1

unread,
Nov 8, 2016, 1:03:45 PM11/8/16
to Elm Discuss
That is expected.  Generally you'd have a uniquely named port per module unless they really do all need to get the message.  It might be better if you describe what you are trying to accomplish instead of how to accomplish it?  :-)

Wouter In t Velt

unread,
Nov 8, 2016, 4:37:19 PM11/8/16
to Elm Discuss
In your design, the port has a 1-to-1 connection to the Part.
The port does not communicate for which Part the incoming message is intended, because it "expects" that their is only 1 Part.

Your batch function in the subscriptions in the Group.elm passes on the port message on to all your Parts.
Your port receives one message of type `PortMsg (Some, Values)`.
The Group subscription function effectively turns this into 10 messages of type `PartMsg 0 PortMsg (Some, Values)` etcetera.
And passes each on Group update function.
So all parts respond to the one message.

You could:
  • refactor your port, to provide also an Int id of the intended Part to receive the message
  • subscribe to this port ONLY in Group
  • that would also mean you need to change code in JavaScript-land, to provide the Id when sending info to Elm
Or:
  • store an `activePart: Int`  in your Group model, so your Group update knows to which single Part the message should go
  • subscribe to the (unchanged) port in your Group subscriptions only (without bundling the subscriptions of each of the Parts)
Or other options.
But that depends very much on what you want to achieve. The terms 'Part'  and 'Group' are quite generic, and it is unknown what happens at the other end of the port.
Maybe you could elaborate on what you want to achieve?


Matthieu Pizenberg

unread,
Nov 10, 2016, 5:11:19 AM11/10/16
to Elm Discuss
Thanks OverminDL1 and Wouter In t Velt, I will try to explain how I have come to this issue.
Originally, I have designed a web page (the "part") that was loading a collection of images to display them.
So the flow was:
 - init the elm page with a list of images urls
 - [port] send the urls to JS
 - JS create local blob copies of the images at these urls
 - [port] JS send back the urls of local images to elm
 - elm can display and manipulate the collection of locally stored images

Everything was working fine.
Then we wanted to deal with multiple collections of images successively.
So the app becomed kind of a SPA (the "Group") where each page (a part) corresponded to one collection.

And so the problem is that each page is receiving a notification for loaded images that should notify only the page corresponding to their own collection.

Currently, I have circumvent the problem by filtering.
Since each page is aware of its own images urls, if it receives a port notification with an new url that does not correspond to one of its own, it does nothing of it.

I have been reading a lot since, and a lot about the problem of making too many "components".
So as you suggested Wouter In t Velt, I am refactoring a lot of my code (and getting rid of a lot of those "components" inside it ^^).
It turns out it is getting much simpler than before. So I think I will go with your first advice once it is finished, and manage all ports from the top level SPA (the "Group").

Thanks again, I will let you know how it is going as soon as I get some time to take care of this.

OvermindDL1

unread,
Nov 10, 2016, 10:37:46 AM11/10/16
to Elm Discuss
Definitely recommend simplifying it yep.  :-)

Ports, in my opinion, should only ever go in one, fully-functional module (I have it in a `Ports.elm` in my projects) that all things should share through it.  The global top-level update call should feel free to delegate messages around.

Wouter In t Velt

unread,
Nov 11, 2016, 5:30:37 AM11/11/16
to Elm Discuss
Yeah, I too would recommend simplicity :) and one Ports.elm module at the top.

My own productivity in Elm has improved enormously since moving to flat instead of nested, and moving away from components.
Hope you will have the same experience going forward!

Witold Szczerba

unread,
Nov 13, 2016, 8:24:16 PM11/13/16
to elm-d...@googlegroups.com
Hi Wouter,
you said:

My own productivity in Elm has improved enormously since moving to flat instead of nested, and moving away from components.

I am trying to get my head around Elm for some time, did not have an opportunity to test drive it until recently. It's still extremely small (literally one page) and easy app. My web app experience so far is mostly AngularJS (since the early/ancient days of 0.9 versions). In AngularJS more or less everything is about "components". Can you provide some real-life example of "moving away from components"? That could be helpful…

Thanks,
Witold Szczerba

--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Wouter In t Velt

unread,
Nov 14, 2016, 4:58:05 AM11/14/16
to Elm Discuss
Op maandag 14 november 2016 02:24:16 UTC+1 schreef Witold Szczerba:
Can you provide some real-life example of "moving away from components"? That could be helpful…

Sure. My background is in React, where everything there is components too.
No examples of my own to share just yet. My app is very much "work in progress", and not yet clean enough to share I am afraid.

In the first version of my app (to plan homework), I would have e.g. an EditExam.elm file that contained its own Model, init, update, Msg and view.
And in my main.elm, I would import this module and integrate each of these into the main Model, init etc.
So basically, EditExam.elm was a "component". With the purpose of displaying an Exam, and allowing user to edit details.

When moving away from components, I created top-level files in my root directory:
  • Model.elm
  • Msg.elm
  • Update.elm
  • View.elm
  • Main.elm (which imports all of the above).
I copied the code bits from the component into each of these files. So for example:
type alias Model =
 
{ ...
 
, exams : List Exam
 
, currentExam : Exam
 
}

type
alias Exam =
 
{ subject : String
 
, ...
 
}

So the Exam type is the Model from the old EditExam.elm file.
Did the same for init, Msg, update, and view.

A (larger) real-life example that I use as a reference is the time-tracker SPA (here on github). Which is a project as part of a DailyDrip series.

A very good explanation (with simple examples) of non-component scaling in Elm is in the Official Guide here.
That explanation helped me a lot, along with the video's on sortable table linked there.

Hope this helps!

Witold Szczerba

unread,
Nov 15, 2016, 5:03:31 PM11/15/16
to elm-d...@googlegroups.com
Thank you, Wouter for explanation. I was reading the documentation, but the chapter covers just the reusable views. View functions seems fine, using modules one can create, group, nest, etc. 

I am wondering how making everything "flat" scales in a long term. Having everything in just 4 files (Model/Mgs/Update/View) would eventually lead to huge, separated silos with all the "business domains" mixed, wouldn't it?

What wrong happens when the application gets divided by features (in opposite to features being divided by those 4 files)?

Maybe when app gets bigger, some kind of hybrid approach would emerge?

Thanks,
Witold Szczerba


--

Wouter In t Velt

unread,
Nov 16, 2016, 4:13:41 AM11/16/16
to Elm Discuss
Op dinsdag 15 november 2016 23:03:31 UTC+1 schreef Witold Szczerba:
Thank you, Wouter for explanation. I was reading the documentation, but the chapter covers just the reusable views. View functions seems fine, using modules one can create, group, nest, etc. 

You are right that the example is mostly about views, but the principles apply to other bits as well.
 
I am wondering how making everything "flat" scales in a long term.

In the SPA I developed, I started out with a simple structure with Main.elm, Update.Elm, Msg.elm, Model.elm.

At some point I needed a datepicker, and built one with Elm. I started of with a DatePicker.elm module, which I put inside a separate Views folder.
The DatePicker also required some additional messages: HandleUpMonth, HandleDownMonth, DateSelected Date. And somewhere in my update, these messages need to be handled. And somewhere in my model, I needed to keep track of the currentMonth, in view in the DatePicker. (so the DatePicker view knows which month to display).
I started out by simply adding stuff to Msg.elm, Update.elm and Model.elm.
Putting the extra info in the model at the highest level on purpose. So my structure remained flat, not nested.

In the next round, I found out that actually processing the selected date required quite a bit of code, to do the correct validations, update the right record in my data structure, and close my DatePicker page.

This was related to the data update, not to the DatePicker, so I created a separate module for Exams, which handles the validation and updates for the exam.

At this point, my folder structure was something like this:

src
|- Main.elm
|- Model.elm
|- Msg.elm
|- Update.elm
|- Updates (folder)
   
|- Exams.elm
|- View.elm
|- Views (folder)
   |- DatePicker.elm


The nice thing about scaling in this way, is that the compiler has your back: once you add messages in Msg.elm, the compiler will complain that you need to change something in your Update.elm, because not all Msg are handled there. So it is not a big deal that your DatePicker view function and the related Msg are in different files. The compiler will protect their consistency.

In short, the way I scale is simply:
  • Only put different pages in different view modules (and import them in the overall View.elm)
  • Everything else: simply put it in Msg.elm, Update.elm, etc. And only if one of these files becomes too big (300-400 lines is my threshold), take out stuff and put it in a separate module.
By now, my app is getting pretty big. But it remains very managable.
I use the DatePicker now in several different places, and the flat structure still works for me.

Hope this helps!

Witold Szczerba

unread,
Nov 16, 2016, 6:30:59 AM11/16/16
to elm-d...@googlegroups.com
Thanks for the extended feedback :)
I will be back here once I have something big enough!

Regards,
Witold Szczerba

--
Reply all
Reply to author
Forward
0 new messages