Transitioning from element to application

I’m trying to make the transition from Browser.element to Browser.application, but I’m struggling to get started.


  1. In the Guide under Structuring Web Apps it says, “I would have a module for each page, centered around the Model type. Those modules follow The Elm Architecture with the typical Model, init, update, view, and whatever helper functions you need.”

A Model in each module? Does this not conflict with the idea of keeping all state in one place?

In Main.elm we tie the main functions together in a record in a Browser.* function. What would tie them together in a (sub-)module?


  1. Under Navigation it says, " When someone clicks a link, like <a href="/home">Home</a>, it is intercepted as a UrlRequest. So instead of loading new HTML with all the downsides, onUrlRequest creates a message for your update where you can decide exactly what to do next."

But my Browser.element app doesn’t have any links. All of my ‘links’ are of the form onClick or onPress which generate messages, sometimes leading to an new view. How, and where, should I replace them?


  1. The Document type
type alias Document msg =
    { title : String
    , body : List (Html msg)
    }

Why does body have the type List (Html msg) and not just simply Html msg?

How should we handle this? There is the obvious [header, main, footer] structure, but there must be a more fundamental reason why it’s a list.


  1. I have tried looking through Richard Feldman’s Elm spa Example, but it’s very complex.

Among other things, it makes extensive use of Html.map. But the documentation for Html.map says, “This should not come in handy too often. Definitely read this before deciding if this is what you want.
(ps this is a broken link).

And the let section of the top level view function has my head spinning.

1 Like

Technically, you can do all of this by hand, but I would not recommend it.

Instead, I would recommend installing elm-spa.

You start by writing elm-spa new, this will generate the main page located at Pages/Home_.elm. And yes, a page should have its own Model, init, update and view. But i would let elm-spa create the wiring for you.

To add a new “about” page, just write elm-spa add /about. You can even pass values like this: elm-spa add /user/:id.

4 Likes
  1. you use a model for each page (PageA.Model, PageB.Model, …) and then have a main-model in your Main.elm with something like this:
type Model
   = PageA PageA.Model
   | PageB PageB.Model

so you have all your state here - depending on which page you are showing one of those.


  1. I would recommend you switch to pages with their own URLs - this is much nicer for the users as they can bookmark or share individual URLs to your pages. So have a URL for each page and decide based on the URL which page you should be showing.

  1. Don’t know but having a List is much nicer - at least I always have more than one element inside body so this keeps you from having a dummy-div [] [ ... ] to wrap this up.

  1. Yes Html.map, Cmd.map and maybe Sub.map is needed to push the pageModel : PageA.Models into PageA pageModel : Model to keep then all on the same main-model - this is boilerplate you really circumvent with the type-system Elm gives you when you want to use the obvious and often used page as components with their own Model, Msg, update, view TEA-architecture.
2 Likes

I would expect this to be a list. If body is the html body tag, then I see the value of body as the children of the body tag, which I would expect to be a list.

1 Like

Another approach would be to use a flat model:

type alias Model =
  { pageA : PageA.Model
  , pageB : PageB.Model
  ...
  // Any other fields your App needs
  ...
  }

This approach is useful if you want to maintain the state of each page, or if you have additional, non-page-specific ‘settings’ that your app utilizes from time to time.

I guess it depends on your need but for my needs (where I want to get to a certain page via a URL and the URL has all the needed initial information to setup that page) this would not work very well and I would not recommend it for an architecture in my teams.

If I need shared state across different pages (I usually do) I add a session : Session field to each pages-model and copy this from the current page to the new on page transitions.

That involves a bit of boiler-plate code but it’s a no-brainer and usually I don’t mind - at least not when working with Elm :wink:

How will that not work very well with a Type Alias? It’s exactly what I do without any issues.

That’s not meant as a criticism, just curious. I’m a self-taught hobbyist that’s been using Elm since 0.18, I’ve got a, very close to, 50k loc Elm project I’ve been working on, and like to keep up with the various ‘how-to’ discussions around best practices etc. One such discussion related to preferring flat models.

I started my current project as you have described, with an Enum Type as the Main model, and passed an additional Session type around. Eventually, I found that there was ‘stuff’ being stored on the Session that I didn’t think should really be there, and it was becoming a bit of a dumping ground. Plus I wanted my pages to maintain their state for the life of the App, for this project that makes sense. So I refactored from an Enum Type to a Type Alias on the Main model, and feel that it is a much better design now.

I don’t mean that to sound like “A Type Alias is better than an Enum Type”, not at all, but for my particular use case, it works for me. Maybe if I refactored again - back to using an Enum Type, the design would improve again… :man_shrugging: :joy:

I’d be interested to learn what you have against a Type Alias for the Main Model, though - might be something the OP would be interested in too.

3 Likes

it’s not about the type alias - it’s about having to construct the full tuple (with all models for each page) for/on every requested page/url - so you need some “invalid” or “empty” page-models for all the other pages.

Overall it’s a representable state (all pages at once) that feels a bit invalid to me - I’d rather have the obvious modeling of the model representing exactly the page shown.

IMHO this is just as flat (meaning no more wrappers) then what the tuple approach would give you.

In the end I don’t think there is a objective right or wrong way to do it (or at least I did not really bother to try and come up with some objective measure of “good” architecture and then went to compare the to approaches against each other) - it’s just what I prefer and the reasoning is more or less what I would give a peer that would ask me my opinion.

2 Likes

@AlanQ have you seen this resource ?

It explains it all :slight_smile:

1 Like

It doesn’t. Here’s an example of the approach in action, i.e. 1 module per page. Main.elm just orchestrates everything.

1 Like

Many thanks for all the interesting replies and explanations, gratefully received.
It looks like elm-spa is the way to go… :slight_smile:
[Edit] …I’ve just discovered Elm Land: Ryan’s upgrade of elm-spa…


I’m very interested in this whole branch of the topic :slight_smile:
Model = Custom Type vs Model = Record.

In a previous question I asked, Is it inefficient to pass the whole model from function to function?

I think a relevant point from this discussion is that a complex structure like the model is passed by reference. So, whether the model is data for each page plus session data inside a record, or a custom type of one page at a time plus session data, there’s no efficiency gain or loss.

Which I think I am typically very likely to need.


Could we somehow use each page’s init to populate the ‘empty’ values?
Alternatively, might the record entry for each page be a Maybe?

I’m still finding my way around elm-spa, but it seems to solve this one by combining Carsten’s and Paul’s approaches — a custom type inside a record — plus auto-generation:
The underlying model is a record in .elm-spa/defaults/Main.elm

module Main exposing (main) 
import Gen.Pages as Pages 
type alias Model =
    { url : Url
    , key : Key
    , shared : Shared.Model
    , page : Pages.Model
    }

Pages.Model = Model.Model:

type Model
    = Redirecting_
    | Home_ Gen.Params.Home_.Params ()
    | NotFound Gen.Params.NotFound.Params

So each time a page is added (elm-spa add), a new option is added to the Model.Model.


Minor note on the Document body type

Even when writing raw html, I always use a ‘dummy’ div because it feels wrong to style the top-level body. And, in the case of the Document type, there is no mechanism to style the body:

  • if it has three unstyled child divs, they sit one above the other
  • style them as float: left and they sit side by side
  • but if we want flexbox styling, we can’t style body as a flex parent. So, for example, everyone using elm-ui ends up with something like body = [ myElmUiBody ].

It’s like a painting studio that favours the painters of triptychs over those who just use a single canvas: every frame comes with a hinge, whether you need it or not :wink:

1 Like

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