Sending messages up the component tree in Elm 0.17

1,292 views
Skip to first unread message

Sandi Dušić

unread,
Jul 7, 2016, 1:50:41 PM7/7/16
to Elm Discuss
In Elm 0.16 the Architecture tutorial had one more button list example (I dug that up from an old commit), aside from this one. Each counter would have it's own remove button which would remove that exact one when clicked, unlike in the simpler example with a sole remove button that removes the first counter. This was accomplished by passing two addresses to the counter view, one for it's own actions and another which it's father component (CounterList) handled, used for signaling removal. 

I did the exact same thing in my application. I have a bunch of small components in a big component, and the big component needs to know when one of the small ones has been clicked. How do you do this in 0.17? Is it impossible, since they removed the example which is supposed to implement it?

This is the only thing I can think of: Add the message that needs to be sent upwards to the big component to the message union of the small component (in the CounterList, this would mean Counter.Msg has Remove). When the big component gets a small component action, it would first check with an if whether it's the one it needs to handle (if msg == Counter.Remove then ...). If so, it can handle it (remove the Counter), otherwise it would just regularly pass it to Counter.update.

To me that seems like it goes against the principles of Elm Architecture. You can no longer treat components (and especially their actions) like black boxes, but rather you have to tear them apart in a way. They cannot fully define their interface. I don't know, it's just weird.

If there's a way to define custom subscriptions they might be leveraged to solve this, but I don't see a way to do that in the API. I apologize if this is a silly question, 0.17 is still new to me.

Mark Hamburg

unread,
Jul 7, 2016, 3:18:57 PM7/7/16
to elm-d...@googlegroups.com
We're still awaiting an official answer from Evan. In the meantime, the best answer I've seen from the standpoint of not contaminating the counter messages and update function with something that they don't care about is to put the complexity in the view function which is, I would argue, where it belongs since this it's a view layout issue that forces the remove button to be included in the counter view. One way to do this is to extend the view function with two arguments: one is a function to map the counter messages to the parent message space and the other is the parent message that the remove button should send.

Mark
--
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...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Sandi Dušić

unread,
Jul 7, 2016, 6:27:32 PM7/7/16
to elm-d...@googlegroups.com
Thank you Mark!

One way to do this is to extend the view function with two arguments: one is a function to map the counter messages to the parent message space and the other is the parent message that the remove button should send.
Would the child view then return Html ParentMsg? In reality, my components are more separated in depth, by four levels. Bit ugly, but seems it's all we have right now. It actually appears elegant in contrast to my approach.

--
You received this message because you are subscribed to a topic in the Google Groups "Elm Discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elm-discuss/H1AUQelu78c/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss...@googlegroups.com.

Mark Hamburg

unread,
Jul 7, 2016, 7:51:01 PM7/7/16
to elm-d...@googlegroups.com
Yes, the view would return Html parentMsg. The signature would be something like:

view: (ChildMessage -> parentMsg) -> parentMsg -> ChildModel -> Html parentMsg

Note that parentMsg is parameterized.

Mark

Alex Lew

unread,
Jul 7, 2016, 9:22:15 PM7/7/16
to Elm Discuss
I wrote about a generalization of that pattern a week or so ago, in case you're interested: https://medium.com/@alex.lew/the-translator-pattern-a-model-for-child-to-parent-communication-in-elm-f4bfaa1d3f98

Another pattern that seems very popular is to have the child's update return the signal to the parent (in your case, a Bool might work -- should I or shouldn't I be removed?) -- Brian Hicks has a great & entertaining blog post about it here: https://www.brianthicks.com/post/2016/06/23/candy-and-allowances-parent-child-communication-in-elm/

best,
Alex

Wouter In t Velt

unread,
Jul 8, 2016, 3:08:28 AM7/8/16
to Elm Discuss
A solution I am currently experimenting with is to wrap the Msg of each component into a union type:

type AddressedMsg =
ToSelf Msg
| ToParent OutMsg
| ToRoot RootMsg

All relevant functions of the component which output ( Model, Cmd Msg ) change to ( Model, Cmd AddressedMsg )

The update function (of any component in the tree) is now:

update : AddressedMsg -> Model -> ( Model, AdressedMsg )
update msg model =
case msg of
ToSelf msg' ->
handleMsg msg' model
ToParent outMsg ->
-- translate outMsg to new message for parent

Inside the view, I bundled my taggers to include the address, e.g. 'ToSelf Increment' to increase counter 'ToParent RemoveMe' to notify parent counter needs to be removed.

Inside parent's update function is similar and the confidentiality is preserved. Whenever the parent of this component is called (e.g Modify 5 msg in case of the counter).
When the Msg is of type ToSelf, then the parent calls the update of the child.

Adam Waselnuk

unread,
Jul 8, 2016, 8:43:36 AM7/8/16
to Elm Discuss
I asked about this as well and tried an approach where you return a three element tuple from the update function. The third element is a message for the parent. This was suggested by Sporto.
I built out a basic working example of that approach here: https://github.com/AWaselnuk/elm-nested-list


Sandi Dušić

unread,
Jul 9, 2016, 10:49:16 AM7/9/16
to elm-d...@googlegroups.com
So many decisions... Really hope Evan announces the official method soon.

2016-07-08 14:43 GMT+02:00 Adam Waselnuk <adam.w...@gmail.com>:
I asked about this as well and tried an approach where you return a three element tuple from the update function. The third element is a message for the parent. This was suggested by Sporto.
I built out a basic working example of that approach here: https://github.com/AWaselnuk/elm-nested-list


Mark Hamburg

unread,
Jul 9, 2016, 5:27:14 PM7/9/16
to elm-d...@googlegroups.com
On Jul 7, 2016, at 6:22 PM, Alex Lew <alexl...@gmail.com> wrote:
>
> I wrote about a generalization of that pattern a week or so ago, in case you're interested: https://medium.com/@alex.lew/the-translator-pattern-a-model-for-child-to-parent-communication-in-elm-f4bfaa1d3f98

I like this approach. It emphasizes that the tagger can be a somewhat more arbitrary translator function — something that common usage patterns obscure. Furthermore, it could be extended to reflect the fact that the translator needed for update results might be different from the translator needed for view results. In particular, the view function in the counter example can be typed as returning an Html (Removable.Msg Counter.Msg) for which it is then the job of the parent view to provide a translator into a parent message.

Mark

Mark Hamburg

unread,
Jul 9, 2016, 6:40:10 PM7/9/16
to elm-d...@googlegroups.com
Here is a quick sketch (untested) of a generic container class for children whose views support generating a remove message. I left out the logic for passing commands up through update for brevity:

type alias Model childModel =
  { nextId : Int, children : List (Int, childModel) }
  

type Message childModel childMessage
  = Child Int childMessage
  | Remove Int
  | Append childModel
  
  
type RemovableMessage childMessage
  = ToThis childMessage
  | RemoveThis
  
  
update :
  (childMessage -> childModel -> childModel)
  -> Message childModel childMessage -> Model childModel -> Model childModel
update childUpdate message model =
  case message of
    Child id childMessage ->
      { model
      | children =
          model.children
          |> List.map (\((childId, childModel) as entry) ->
                if childId == id then
                  (childId, childUpdate childMessage childModel)
                else
                  entry)
      }
    Remove id ->
      { model
      | children = List.filter (fst >> ((/=) id)) model.children
      }
    Append child ->
      { model
      | nextId = model.nextId + 1
      , children = List.append model.children [ ( model.nextId, child ) ]
      }
    
    
view :
  (childModel -> Html.Html (RemovableMessage childMessage))
  -> Model childModel
  -> Html.Html (Message childModel childMessage)
view viewChild model =
  Html.div [] <| List.map (viewEntry viewChild) model.children
  

viewEntry :
  (childModel -> Html.Html (RemovableMessage childMessage))
  -> (Int, childModel)
  -> Html.Html (Message childModel childMessage)
viewEntry viewChild (childId, childModel) =
  Html.App.map (translateRemovableMessage childId) <| viewChild childModel


translateRemovableMessage :
  Int -> RemovableMessage childMessage -> Message childModel childMessage
translateRemovableMessage childId removableMessage =
  case removableMessage of
    ToThis childMessage -> Child childId childMessage
    RemoveThis -> Remove childId
 

The RemovableMessage type essentially serves as our interface to the child view function and could in fact be part of the declaration for that function or stored elsewhere as a generally useful concept that might work with multiple container and contained types.

This case is arguably cleaner than the Elm 0.16 solution. On the other hand, it's going to get a bit messy if one has to pass values up through several layers. In Elm 0.16, that could be handled by providing a Signal.Message to handle the message that needed to go somewhere other than the "corresponding" model. But maybe the strength here is in putting some separation between the model hierarchy and the view hierarchy and weakening the notion that there is a one-to-one correspondence all the way down.

Mark
Reply all
Reply to author
Forward
Message has been deleted
0 new messages