Elm Language

The Elm Architecture

Introduction#

The recommended way to structure your applications is dubbed ‘the Elm Architecture.’

The simplest program consists of a model record storing all data that might be updated, a union type Msg that defines ways your program updates that data, a function update which takes the model and a Msg and returns a new model, and a function view which takes a model and returns the HTML your page will display. Anytime a function returns a Msg, the Elm runtime uses it to update the page.

Beginner program

Html has beginnerProgram mostly for learning purposes.

beginnerProgram is not capable of handling Subscriptions or running Commands.

It is only capable of handling user input from DOM Events.

It only requires a view to render the model and an update function to handle state changes.

Example

Consider this minimal example of beginnerProgram.

The model in this example consists of single Int value.

The update function has only one branch, which increments the Int, stored in the model.

The view renders the model and attaches click DOM Event.

See how to build the example in Initialize and build

import Html exposing (Html, button, text)
import Html exposing (beginnerProgram)
import Html.Events exposing (onClick)


main : Program Never
main =
    beginnerProgram { model = 0, view = view, update = update }


-- UPDATE


type Msg
    = Increment

update : Msg -> Int -> Int
update msg model =
    case msg of
        Increment ->
            model + 1


-- VIEW


view : Int -> Html Msg
view model =
    button [ onClick Increment ] [ text ("Increment: " ++ (toString model)) ]

Program

program is a good pick, when your application does not require any external data for initialization.

It is capable of handling Subscriptions and Commands, which enables way more opportunities for handling I/O, such as HTTP communication or interop with JavaScript.

The initial state is required to return start-up Commands along with the Model.

The initialization of program will require subscriptions to be provided, along with model, view and update.

See the type definition:

program :
    { init : ( model, Cmd msg )
    , update : msg -> model -> ( model, Cmd msg )
    , subscriptions : model -> Sub msg
    , view : model -> Html msg
    }
    -> Program Never

Example

The simplest way to illustrate, how you can use Subscriptions is to setup a simple Port communication with JavaScript.

See how to build the example in Initialize and build / Embedding into HTML

port module Main exposing (..)

import Html exposing (Html, text)
import Html exposing (program)


main : Program Never
main =
    program
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }


port input : (Int -> msg) -> Sub msg


-- MODEL


type alias Model =
    Int


init : ( Model, Cmd msg )
init =
    ( 0, Cmd.none )


-- UPDATE


type Msg = Incoming Int


update : Msg -> Model -> ( Model, Cmd msg )
update msg model =
    case msg of
        Incoming x ->
          ( x, Cmd.none )


-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg
subscriptions model =
    input Incoming


-- VIEW


view : Model -> Html msg
view model =
    text (toString model)
<!DOCTYPE html>
<html>
    <head>
        <script src='elm.js'></script>
</head>
    <body>
    <div id='app'></div>
    <script>var app = Elm.Main.embed(document.getElementById('app'));</script>
    <button onclick='app.ports.input.send(1);'>send</button>
</body>
</html>

Program with Flags

programWithFlags has only one difference from program.

It can accept the data upon initialization from JavaScript:

var root = document.body;
var user = { id: 1, name: "Bob" };
var app = Elm.Main.embed( root, user );

The data, passed from JavaScript is called Flags.

In this example we are passing a JavaScript Object to Elm with user information, it is a good practice to specify a Type Alias for flags.

type alias Flags =
    { id: Int
    , name: String
    }

Flags are passed to the init function, producing the initial state:

init : Flags -> ( Model, Cmd Msg )
init flags =
    let
        { id, name } =
            flags
    in
        ( Model id name, Cmd.none )

You might notice the difference from it’s type signature:

programWithFlags :
    { init : flags -> ( model, Cmd msg )          -- init now accepts flags
    , update : msg -> model -> ( model, Cmd msg )
    , subscriptions : model -> Sub msg
    , view : model -> Html msg
    }
    -> Program flags

The initialization code looks almost the same, since it’s only init function that is different.

main =
    programWithFlags
        { init = init
        , update = update
        , view = view
        , subscriptions = subscriptions
        }

One way parent-child communication

Example demonstrates component composition and one-way message passing from parent to children.

Component composition relies on Message tagging with Html.App.map

In 0.18.0 HTML.App was collapsed into HTML

Component composition relies on Message tagging with Html.map

Example

See how to build the example in Initialise and build

module Main exposing (..)

import Html exposing (text, div, button, Html)
import Html.Events exposing (onClick)
import Html.App exposing (beginnerProgram)


main =
    beginnerProgram
        { view = view
        , model = init
        , update = update
        }

{- In v0.18.0 HTML.App was collapsed into HTML
   Use Html.map instead of Html.App.map
-}
view : Model -> Html Msg
view model =
    div []
        [ Html.App.map FirstCounterMsg (counterView model.firstCounter)
        , Html.App.map SecondCounterMsg (counterView model.secondCounter)
        , button [ onClick ResetAll ] [ text "Reset counters" ]
        ]


type alias Model =
    { firstCounter : CounterModel
    , secondCounter : CounterModel
    }


init : Model
init =
    { firstCounter = 0
    , secondCounter = 0
    }


type Msg
    = FirstCounterMsg CounterMsg
    | SecondCounterMsg CounterMsg
    | ResetAll


update : Msg -> Model -> Model
update msg model =
    case msg of
        FirstCounterMsg childMsg ->
            { model | firstCounter = counterUpdate childMsg model.firstCounter }

        SecondCounterMsg childMsg ->
            { model | secondCounter = counterUpdate childMsg model.secondCounter }

        ResetAll ->
            { model
                | firstCounter = counterUpdate Reset model.firstCounter
                , secondCounter = counterUpdate Reset model.secondCounter
            }


type alias CounterModel =
    Int


counterView : CounterModel -> Html CounterMsg
counterView model =
    div []
        [ button [ onClick Decrement ] [ text "-" ]
        , text (toString model)
        , button [ onClick Increment ] [ text "+" ]
        ]


type CounterMsg
    = Increment
    | Decrement
    | Reset


counterUpdate : CounterMsg -> CounterModel -> CounterModel
counterUpdate msg model =
    case msg of
        Increment ->
            model + 1

        Decrement ->
            model - 1

        Reset ->
            0

Message tagging with Html.App.map

Components define their own Messages, sent after emitted DOM Events, eg. CounterMsg from Parent-child communication

type CounterMsg
    = Increment
    | Decrement
    | Reset

The view of this component will send messages of CounterMsg type, therefore the view type signature is Html CounterMsg.

To be able to reuse counterView inside parent component’s view, we need to pass every CounterMsg message through parent’s Msg.

This technique is called message tagging.

Parent component must define messages for passing child messages:

type Msg
    = FirstCounterMsg CounterMsg
    | SecondCounterMsg CounterMsg
    | ResetAll

FirstCounterMsg Increment is a tagged message.

To get a counterView to send tagged messages, we must use the Html.App.map function:

Html.map FirstCounterMsg (counterView model.firstCounter)

The HTML.App package was collapsed into the HTML package in v0.18.0

To get a counterView to send tagged messages, we must use the Html.map function:

Html.map FirstCounterMsg (counterView model.firstCounter)

That changes the type signature Html CounterMsg -> Html Msg so it’s possible to use the counter inside the parent view and handle state updates with parent’s update function.


This modified text is an extract of the original Stack Overflow Documentation created by the contributors and released under CC BY-SA 3.0 This website is not affiliated with Stack Overflow