Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Composing Programs in the Elm Architecture

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?

like image 638
Agnishom Chattopadhyay Avatar asked Dec 14 '22 15:12

Agnishom Chattopadhyay


1 Answers

I think @dwaynecrooks covered the technical aspects of the question. But I believe your problem implies a design aspect too.


How to grow Elm code?

As others pointed out: thinking in terms of components will almost certainly take you down a not so appealing path with Elm. (Many people start there. I and my team started there over 2 years ago, and it took us 3 apps/major redesigns to get to a point where I think we can be happy at least about the fundamentals.)

Instead of components, I suggest you should think of your Elm application as a tree. Each node of your tree represents a level of abstraction, and describes the behavior of your application on that level. When you have the feeling that there is too much detail for a given level, you can start thinking about how new, lower levels of abstraction could be introduced as child nodes.

In practice each node is implemented in its own Elm module: parents import their children. You may also consider that you don't have to stick to the usual model/update/view signatures, rather you should focus on the particularities of your app's domain. This is what – in my read – Richard Feldman is doing in his Real World SPA example app. And Evan's Life of a file talk is related to this question too.


The case of navbar + body

Regarding your particular case – it is not a rare case – here is my experience. If we say that our webapp has a navbar and then some body, this is a pretty static description of the app. This kind of description may fit a component based thinking, but it is less helpful if you want to end up with an elegant Elm app.

Instead, it is worth trying to describe the behavior of your app at this level of abstraction, which may sound something like this: The user can select x,y,z items in a navbar. Clicking on those items will affect the item in q way and also affect the body in either a, or b way. He can also click on v in the navbar, which would show a popup or do w, which logs him out of the app.

If you take this description and apply the logic that I described above, you should probably end up with some sort of a design where most of your navbar is described in your highest level of abstraction. This includes items x, y, z, v and behaviors a, b, w. Now, behavior a may mean that a specific, rich page must be displayed, which has its own detailed behavior that is described on a lower level of abstraction, whereas behavior b may mean that based on the selection some content must be loaded, and again the details of this loading process is worked out on a lower level of abstraction. And so on.

When we started approaching the problem this way, it became much more straight forward to find out how to split up logic, and how to deal with special cases. We realized, for instance, that when someone said that a page wants to display some stuff "in the navbar", what she really meant was that the navbar should collapse (or transform) for a particular page so that page can display it's own header in that area.

Focusing on the app's behavior instead of static content areas helped with this.

like image 109
Gabor Avatar answered Dec 24 '22 21:12

Gabor