Suppose I want to create a webpage with two components, say a Navbar
and a Body
. These two components do not interact with each other and can be developed independently. So, I have two elm files which have the following components in each of them:
type Model = ...
type Msg = ...
init : (Model, Cmd Msg)
update : Msg -> Model -> (Model, Cmd Msg)
view : Model -> Html Msg
Assumming that they both work fine, how do we compose them to make a program which has both these components?
I tried writing something like this:
type Model = {body : Body.Model , navbar : Navbar.Model}
type Msg = BodyMsg Body.Msg | NavbarMsg Navbar.Msg
view : Model -> Html Msg
view model = div [] [Body.view model.body, Navbar.view model.navbar]
update : Msg -> Model -> (Model, Cmd Msg)
update = ...
The above gets ugly quickly when I try to write this update function. In particular, once I extract the Msg's out of the Cmd's update functions from Navbar.update
or Body.update
, how do I extract them and feed them back to these functions again? Also, the view function above does not particularly look idiomatic.
What is the elm-architecture recommended way to solve this problem? Is this pattern idiomatic in elm-architecture?
Yes, you're on the right path.
In the view you need to use Html.map
.
view : Model -> Html Msg
view model =
div []
[ Html.map BodyMsg (Body.view model.body)
, Html.map NavbarMsg (Navbar.view model.navbar)
]
Body.view model.body
has type Html Body.Msg
which requires us to use Html.map
to get the correct type of Html Msg
. Similarly for Navbar.view model.navbar
.
And, for the update
function you'd write it like so:
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
BodyMsg bodyMsg ->
let
(newBody, newCmd) = Body.update bodyMsg model.body
in
{ model | body = newBody } ! [ Cmd.map BodyMsg newCmd ]
NavbarMsg navbarMsg ->
let
(newNavbar, newCmd) = Navbar.update navbarMsg model.navbar
in
{ model | navbar = newNavbar } ! [ Cmd.map NavbarMsg newCmd ]
In the BodyMsg
case, newBody
has type Body.Model
and so we can set the body
field in model
to it. However, newCmd
has type Cmd Body.Msg
and so before we can return we need to use Cmd.map
to get the correct return type of Cmd Msg
.
Similar reasoning can be used for the NavbarMsg
case.
Also, the view function above does not particularly look idiomatic.
What bothers you about the view code?
N.B. This answer assumes you're using Elm 0.18.