Translator

As described in Nested TEA it is sometimes useful to create a nested Elm architecture. When making this we normally would use Html.map to route messages back to the nested module.

For example, in the parent container you would have:

module Parent exposing(..)

import Child

Child.view model.childModel |> Html.map ChildMsg

The challenge of using Html.map here is that messages produced in the child module always need to route back to itself. We cannot easily produce a message in the child destined to its parent.

As an application grows it is common to encounter something like this, perhaps we need a UI element in the child module that needs to send the message to its parent.

Pattern

The solution to this is to make the child module views generic and provide a constructor for routing messages. So, instead of View Msg in the child module, it becomes View msg. Then the parent explicitly provides the constructor to route messages.

For example, we have a child module with a view like:

module Child exposing(..)

view: Model -> Html Msg
view model =
  div []
  [ button [ onClick Clicked ] [ text model.name ]
  ]

We want to add another button, but this time it should send a message to its parent.

First, make the child module generic

module Child exposing(..)

view: Model -> (Msg -> msg) -> Html msg
view model toSelf =
  div []
  [ button [ onClick (toSelf Clicked) ] [ text model.name ]
  ]

To toSelf is a constructor that wraps the internal message, producing a parent message. This replaces Html.map in the parent module and will route the message back to this module.

In the parent container we would use this view like:

module Parent exposing(..)

import Child

type Msg = ChildMsg Child.Msg

view model =
  ...
  Child.view model.childModel ChildMsg

Produce a parent message

With this setup we can produce parent messages from the child module.

module Child exposing(..)

type alias Args msg =
  { toSelf : Msg -> msg
  , onSave: msg
  }

view: Model -> Args msg -> Html msg
view model args =
  div []
  [ button [ onClick (args.toSelf Clicked) ] [ "Send to self" ]
  , button [ onClick args.onSave ] [ text "Send to parent" ]
  ]

The parent would call this like:

module Parent exposing(..)

import Child

type Msg
  = OnSave
  | ChildMsg Child.Msg

view model =
  ...
  Child.view
    model.childModel
    { toSelf = ChildMsg
    , onSave = OnSave
    }