My previous post about child-parent communication has done very well. It was also pretty dry and technical. Since then, Brian Hicks has done a much better job at explaining how to use the pattern in practice.
OutMessage
The primary problem with OutMsg is the boilerplate. Updated components, their Cmd
s, and their OutMsg
s all need to be routed to the correct places, which is cumbersome and prone to error.
Enter OutMessage. This package removes the boilerplate, and makes it easy to switch between OutMsg
, Maybe OutMsg
and List OutMsg
. For the maybe case, you can give a default Cmd
, and in the list case all the intermediate updating is taken care of.
-- in update : Msg -> Model -> (Model, Cmd Msg)
-- assuming interpretOutMsg : OutMsg -> Model -> (Model, Cmd Msg)
ChildComponentMessageWrapper childMsg ->
ChildComponentModule.update childMsg model.child
-- update the model with the new child component
|> OutMessage.mapComponent
(\\newChild -> { model | child = newChild }
-- convert child cmd to parent cmd
|> OutMessage.mapCmd ChildComponentMessageWrapper
-- apply outmsg changes
|> OutMessage.evaluate interpretOutMsg
Update logic for a child component using OutMessage
Should your parent and child communicate at all?
Richard Feldman raised this question in the elm slack channel. In his experience, this is only rarely needed. From the resulting discussion, I sensed that it is quite common to need all sorts of communication between components in various javascript frameworks. In Richard’s experience, this is much less the case in elm, making the questions about child-parent communication a manifestation of the XY problem.
The need for bidirectional communication depends highly on the type of application you’re writing. Certain types of applications (in particular SPA’s) naturally need components, others do not. Not all components need to communicate with the parent: A datepicker does, a twitter timeline does not. Applications are much simpler without the bidirectional communication, therefore the default should be to not use it.
So, if you think you need child-parent communication, reconsider. If you really need it, the elm community has patterns and libraries to make it pleasant.
Exploring the Translator pattern
Shortly after my post, Alex Lew also described a pattern for child-parent communication: The Translator pattern. To see how the two compare, I rewrote an example using Translator to use OutMsg.
The code can be found here, the changes to switch between Translator and OutMsg are in this commit
Please note that I am biased in favor of OutMsg.
Rewriting experience
It turns out that it is quite easy to switch between Translator and OutMsg. This is great, because it makes experimenting with different approaches much easier.
The main difference that I see is where most of the logic is handled. OutMsg has logic in the child’s update, to send the correct OutMsg
at the correct moment. Translator seems to have much more. My problem with that is not the code size – much of it can be put in a package –, but the philosophy: Translator learns the child to work with the parent, where OutMsg learns the parent to work with the child. The latter seems much more natural to me.
The parent looks pretty similar, but OutMsg does not need extra Msg
constructors. The result is separation/fragmentation, depending on your viewpoint.
Response to Alex Lew’s article
Alex gives two critiques of the OutMsg pattern:
OutMsg is verbose
This has been solved by OutMessage now, but at the time was a very valid concern.A child can see events that it doesn’t need to handle
When is this a problem? I can think of only one case: the seeing introduces boilerplate.But, when a component has enough events that only its parent should handle to make that boilerplate a problem, maybe your component should not be a component. Instead, it would be better to integrate the component into the parent model. You could also make a
Msg
specifically for sending something to a parent:type OutMsg = ShowPopup type Msg = SendToParent OutMsg update msg model = case msg of SendToParent m -> (model, Cmd.none, m)
But when you require something like the above, maybe you should reevaluate whether you need an independent component.
My concerns with Translator
Please note again, horribly biased.
Every
OutMsg
in the child is aMsg
in the parent
This does not scale. Say you have three different child components with threeOutMsg
s each, The parent update function needs at least 9 constructors.In contrast,
Msg
using OutMsg scales linearly with the number of components, not with the number ofOutMsg
s these components have.Moves away further from The Elm Architecture than OutMsg does
This is not as ‘bad’ as my structural/scalability concerns, but still valid I think. OutMsg at its core is “add a third output value to a child’s update that the parent may handle”. I struggle to come up with a one-sentence description of Translator.
Conclusion
I think there are some structural problems with the Translator pattern. Then again, I’m quite invested in the OutMsg idea. I’d love to see some more actual examples of using both patterns, to see where the pain points are. Richard Feldman also mentioned that Evan is working on a non-trivial component example, so this is a very active area of experimentation.