Using multiple "instances" of modules?

Hi!

I’m trying to understand the basic strategies for separating concerns of a program in separate modules. I followed this topic: A simple example of splitting view/update into a separate module - and I get the idea.

That topic has an example of a self-contained “counter” module called Button.elm. The module’s view and update is used in Main.elm once. Now I’m wondering what the approach would be for reusing Button.elm twice (or more) in Main.elm? How should Main.elm look if it should render two counters that increment/decrement two independent values?

Model would have two buttons, and you would also need different messages for the buttons to know which was clicked.

For example like this:

module Main exposing (Model, Msg(..), init, main, update, view)

import Browser
import Button
import Html exposing (Html, button, div, h1, map, text)
import Html.Events exposing (onClick)


main =
    Browser.sandbox { init = init, update = update, view = view }



-- MODEL


type alias Model =
    { buttonA : Button.Model
    , buttonB : Button.Model
    }


init : Model
init =
    { buttonA = Button.init
    , buttonB = Button.init
    }



-- UPDATE


type Msg
    = ButtonAMsg Button.Msg
    | ButtonBMsg Button.Msg


update : Msg -> Model -> Model
update msg model =
    case Debug.log "update msg" msg of
        ButtonAMsg buttonMsg ->
            { model | buttonA = Button.update buttonMsg model.buttonA }

        ButtonBMsg buttonMsg ->
            { model | buttonB = Button.update buttonMsg model.buttonB }



-- VIEW


view : Model -> Html Msg
view model =
    div []
        [ h1 [] [ text "elm button example" ]
        , map ButtonAMsg (Button.view model.buttonA)
        , map ButtonBMsg (Button.view model.buttonB)
        ]

You could also use single message type with extra parameter showing which button was clicked:

...
type Msg
    = ButtonMsg Int Button.Msg
...
    case Debug.log "update msg" msg of
        ButtonMsg 1 buttonMsg ->
            { model | buttonA = Button.update buttonMsg model.buttonA }
...
        , map (ButtonMsg 1) (Button.view model.buttonA)
...

@malaire Thank you, that makes perfect sense!

My first thought was your second suggestion: adding a parameter to the message. But that would have meant I need to pass the parameter to the Button view, which I felt would be kind of messy and interfere with the “self-contained-ness” of the Button module.

The trick I didn’t consider was simply defining different message types for the different buttons. That is spot on!

You wouldn’t be passing the extra message parameter to Button.view, as it is used only in Model.view.

Still the extra parameter should be selected so that there are no impossible states. For example Int I chose above is bad example here as it allows e.g. 99 even when there are only two buttons.

If there can be arbitrary number of buttons, then Int can make sense.

Ah I see, you are defining either two distinct new message types (ButtonAMsg, ButtonBMsg) or a new “indexed” message type (ButtonMsg Int Button.Msg). Now I see how you mean that the parameter never has to go to Button.view.

Yes I agree the latter solution (message with index) makes more sense if there can be an arbitrary number of independent counters. Good to know the solution for that case as well!

Just a quick mental heads up in terms of thinking in Elm, in general, you don’t (want? Need?) to think about things as being “self-contained” (e.g. React, etc.).

That’s kind of an anti-pattern in Elm, and will fight TEA (the elm architecture).

Pass as much stuff around as you need! Don’t try to “hide state” in a “component”. Just have one gigantic model with a ton of stuff in it. All good. The compiler will make sure it’s all sorted out! Yay!

:smile:

@madasebrof Good points! Although… while the compliler might be able to make sense of a humongous model, my poor brain might not :wink: (But I haven’t hit that point with Elm yet so my question is just academic, really…)

In general, my main driver for splitting things apart is so that I can work on one problem/feature at a time without needing to scan through or think about (or even scroll past!) things that don’t pertain to the issue at hand. Seems like the advice in the elm-guide of using modules to group things that all pertain to a particular custom type is a good heuristic for that.

1 Like

Did you watch Richard Feldman’s talk that is mentioned in the post you linked to? I’l link it again here:

Good that you are exploring this ‘academically’ as it is all stuff you need to get your head around to use Elm effectively. Richard’s talk is helpful to build an understanding of if and when you need to do this, and what generally better alternatives there are that should be explored first.

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.