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.
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:
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
Now add the following code to
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
The constant above is what actually creates our initial model.
Next, we need to present our initial model to the user. Add the following code to the bottom of
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
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.
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
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.
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
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.
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
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
view. Let’s define the
update function too. Add the following code right above the
main function in
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:
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
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
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.
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
Int in the
update function’s type annotations like this:
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.
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
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:
Msg, we’ve made the
update function much more restrictive. It now accepts only two messages:
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
- buttons are clicked.
The message type changed from
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
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.
As mentioned in the
elm-make section, the
warn flag tells
elm-make will overwrite the
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.
Copy and paste the type annotation from the warning right above the
main function in
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,
Html.Html msg as its type.
In the Fuzz Testing section, its type was
main function takes the type of whatever expression it happens to return. We already know what
Html.Html msg and
Program Never Model Msg means an Elm program that has a model of type
Model and accepts messages of type
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.
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: