5.2

Model View Update - Part 1

On a very high level, web applications tend to have two major components: state and user interface (UI). An application starts with an initial state and presents that state to the user through UI. The user takes some action through a UI element that modifies the initial state. The new state is then presented back to the user for more actions. The figure below shows the interaction between a state and UI in a hypothetical application that allows logged in users to create blog posts.

At any given point in time, an application needs to store different types of information in memory. For example, it needs to know whether or not a user is logged in or how many blogs a user has posted. State is like a repository for storing all of this information. This state is then made available to various data structures in the application. Functions in the application perform different operations on it, resulting into a new state.

In the Pure Functions section, we defined a state as something that represents all the information stored at a given instant in time that a function has access to. Conceptually, an application state works the same way. It just contains a lot more information than a function level state.

Model

In Elm we represent the state of an application with something called a model. A model is just a data structure that contains important information about the application. Imagine a simple app that allows us to increment or decrement a counter. For this app, the only state we need to track is the current value of the counter. Here is what the model definition for this app looks like:

type alias Model
    = Int

It’s just an Int. A model doesn’t necessarily have to be complicated. It all depends on how complex the app is and how many different things it needs to track. For a simple counter app, all we need is a number that tells us what the current value is. A model is generally defined as a type alias.

Let’s add the above model definition into a file and start building a counter app in Elm. Create a new file called Counter.elm in the beginning-elm/elm-examples directory.

Now add the following code to Counter.elm.

module Counter exposing (..)


type alias Model =
    Int

The definition above doesn’t create a model. All it tells Elm is what our model looks like. Add the following code to the bottom of Counter.elm.

initialModel : Model
initialModel =
    0

The constant above is what actually creates our initial model.

View

Next, we need to present our initial model to the user. Add the following code to the bottom of Counter.elm.

view : Model -> Html msg
view model =
    div []
        [ button [ onClick Decrement ] [ text "-" ]
        , text (toString model)
        , button [ onClick Increment ] [ text "+" ]
        ]

The view function takes a model and returns HTML code. Behind the scenes, the div function in Elm produces the <div> element in HTML, and the button function produces the <button> element. The text function doesn’t represent any HTML tag. It just displays a plain text by escaping special characters so that the text appears exactly as we specify in our code.

The first argument to div and button represent a list of attributes. The second argument represents a list of nested elements. The Elm code in the view function is equivalent to the following HTML code.

<div>
    <button> + </button>
    String representation of our model
    <button> - </button>
</div>

The div, button, and text functions are all defined in the Html module which is a part of an Elm package called elm-lang/html that gives us full access to HTML through normal Elm functions. So we need to import the Html module in Counter.elm.

module Counter exposing (..)

import Html exposing (..)
.
.

Since we can treat HTML elements as plain old Elm functions, we can apply all the nice things Elm has to offer to the view code as well. For example, we can refactor the duplicate code out into separate functions and reuse them from different places in our app. We can write automated tests for the view code using the same tools used for testing any other Elm code. Elm compiler will even let us know if we made any mistake in the view code.

The view function isn’t responsible for rendering the HTML on a screen. All it does is take a model and return a chunk of HTML. It’s a pure function that returns the same HTML code given the same model. To actually render the HTML on a screen, Elm uses a package called elm-lang/virtual-dom behind the scenes. Don’t worry about how this package works now. We’ll explore it in detail in the Virtual DOM section later in this chapter.

elm-lang/virtual-dom is a dependency for the elm-lang/html package, so it was automatically installed when we asked elm-package to install elm-lang/html back in the Building a Simple Page with Elm section.

We don’t need to import any of the modules included in the elm-lang/virtual-dom package in our code because we shouldn’t be using it directly in our code. It only exists to support the modules defined in the elm-lang/html package.

The type annotation for the view function indicates that it returns a value of type Html msg which means the HTML code generated by the view function is capable of producing messages of type msg. Don’t worry about understanding what this all means yet; we’ll come back to it soon.

view : Model -> Html msg

Application Entry Point

To display the view, we need to define an entry point to our app. Add the following code to the bottom of Counter.elm.

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

As usual the main function acts as an entry point to our app. It uses the beginnerProgram function defined in the Html module to provide necessary information needed by the Elm runtime to render our view in a browser. We’ve already defined initialModel and view. Let’s define the update function too. Add the following code right above the main function in Counter.elm.

update : msg -> Model -> Model
update msg model =
    initialModel


main =
    ...

The update function simply returns the initial model. We’ll expand it to be more meaningful in a bit. Finally, we’re ready to display our view. Run elm-reactor from the beginning-elm directory in terminal if it’s not running already, and go to this URL in your browser: http://localhost:8000/elm-examples/Counter.elm. You should see something like this in the top left corner of your browser:

Update

Our app is utterly uninteresting at the moment. The buttons don’t do anything. That’s because we haven’t specified what should happen when the buttons are clicked. Let’s do that next. The first thing to do is define messages that represent the actions users can take. Add the following type definition right above the update function in Counter.elm.

type Msg
    = Increment
    | Decrement


update =
    ...

The new type Msg represents the messages our app can respond to. It’s a simple union type with two constants. When the + button is clicked, our app will receive the message called Increment and clicking the - button will generate the Decrement message.

The term “message” doesn’t have any special meaning in Elm. It’s not a special type or a data structure. We could have very well called it “action” or “event” or “do this thing”. The official documentation and the Elm community prefer the term “message” over others. So we should stick with it.

Unlike Model, we didn’t define Msg as a type alias because there’s no built-in type in Elm that can correctly represent our messages. In contrast, our Model is just an int value. So we made it a type alias that simply redefines the existing Int type. You may wonder why do we even need to define Model. We could simply replace Model with Int in the initialModel and update function’s type annotations like this:

initialModel : Int
initialModel =
    0


update : msg -> Int -> Int
update msg model =
    initialModel

And everything should work as before. That’s a valid point. However, by defining Model, we’ve given a name to the value that flows through our app. This makes our code much more readable. In a simple app like our counter, the benefits of naming things properly aren’t huge, but in a large application, a well named domain concept such as Model can add tremendous value from maintenance standpoint.

Let’s modify the update function so that it increments or decrements the model based on which message the app receives.

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

        Decrement ->
            model - 1

We made a subtle change to the update function’s type annotation. The message is now represented by Msg instead of msg which is just a type variable. Unlike number, msg doesn’t have any special meaning in Elm. We could have used any random name to represent a generic message in the previous version like this:

update : someMessage -> Model -> Model
update msg model =
    initialModel

By replacing msg with Msg, we’ve made the update function much more restrictive. It now accepts only two messages: Increment and Decrement. Before, it could accept any message.

We’ve created a mechanism for handling messages, but we still don’t have a way to create them. Modify the view function to fire messages when the + and - buttons are clicked.

view : Model -> Html Msg
view model =
    div []
        [ button [ onClick Decrement ] [ text "-" ]
        , text (toString model)
        , button [ onClick Increment ] [ text "+" ]
        ]

The message type changed from msg to Msg in the view function too. Previously, the HTML returned by view wasn’t creating any message. So we just used msg. Now, it uses the onClick function from the Html.Events module to generate messages of type Msg. Import the Html.Events module in Counter.elm.

module Counter exposing (..)

import Html exposing (..)
import Html.Events exposing (..)
.
.

Refresh the page at http://localhost:8000/elm-examples/Counter.elm and you should be able to increment and decrement the counter.

The Elm Architecture essentially boils down to these three parts: Model, View, and Update. The entire application can be viewed as one giant machine that runs in perpetuity. It takes an initial model, presents it to the users, lets them issue messages from the view, updates the model based on those messages, and presents the updated model back to the users again.

The diagram below illustrates the interaction between the Elm runtime and various components in our app.

main Function’s Type Annotation

Not sure if you noticed, but we didn’t include the main function’s type annotation when we defined it. Let’s ask Elm what the type should be. Delete the build-artifacts directory located inside beginning-elm/elm-stuff and run the following command from the beginning-elm directory in terminal.

elm-make elm-examples/Counter.elm --output counter.js --warn

As mentioned in the elm-make section, the warn flag tells elm-make to print warnings in addition to errors. Don’t forget to mention the output JavaScript file (counter.js). Otherwise, elm-make will overwrite the beginning-elm/index.html.

elm-make doesn’t recompile our code if nothing has changed since the last compilation. By deleting the build-artifacts directory, we’re forcing it to recompile. Without recompilation, the warnings won’t be displayed.

========================= WARNINGS ==============================
---------------- missing type annotation ------------------------

Top-level value `main` does not have a type annotation.

29| main =
    ^^^^
I inferred the type annotation so you can copy it into your code:

main : Program Never Model Msg

Copy and paste the type annotation from the warning right above the main function in Counter.elm.

main : Program Never Model Msg
main =
    beginnerProgram
    .
    .

You can use that trick in the future if you can’t figure out a function’s type annotation from its implementation. The main function doesn’t have one specific type. Back in the Easier Code Organization section, main had Html.Html msg as its type.

main : Html.Html msg
main =
    MyList.isEmpty list1
        |> toString
        |> Html.text

In the Fuzz Testing section, its type was TestProgram.

main : TestProgram
main =
    run <|
        describe "Test suite"
            [ RippleCarryAdderTests.allTests
            , FuzzTests.allTests
            ]

Basically, the main function takes the type of whatever expression it happens to return. We already know what Html.Html msg and TestProgram mean. Program Never Model Msg means an Elm program that has a model of type Model and accepts messages of type Msg. The Never type indicates that no values are passed to the program when it’s initialized. In the Ports section, we’ll see an example that shows how to pass values to an Elm program during initialization.

Summary

In this section, we learned that the Elm Architecture boils down to three fundamental concepts: Model, View, and Update. In the next section, we’ll find out what virtual DOM really is and what benefits we get from it. Here’s the entire code for the counter app for your reference:

module Counter exposing (..)

import Html exposing (..)
import Html.Events exposing (..)


-- Model


type alias Model =
    Int


initialModel : Model
initialModel =
    0



-- View


view : Model -> Html Msg
view model =
    div []
        [ button [ onClick Decrement ] [ text "-" ]
        , text (toString model)
        , button [ onClick Increment ] [ text "-" ]
        ]



-- Update


type Msg
    = Increment
    | Decrement


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

        Decrement ->
            model - 1



-- Entry point


main : Program Never Model Msg
main =
    beginnerProgram
        { model = initialModel
        , view = view
        , update = update
        }
Back to top

New chapters are coming soon!

Sign up for the Elm Programming newsletter to get notified!

* indicates required
Close