Working with Elm: Applying Signals and Actions to our App

Brandon Richey
7 min readJan 11, 2016

--

The Elm Tangram

Introduction

We spent a good amount of time in the last post just trying to better understand the core concepts that drive Elm application (Signals, Mailboxes, Addresses, and Actions). Now, we’re ready to start applying that into building a front end with some real interactivity! You can grab the application we’re going to be expanding from here.

Current Version

Elm: v0.16.0

Putting It All Together

We have to take our original application and start adding new bits of code to support our mailboxes, addresses, actions, and signals. We’ll start by defining our Action union type:

-- UPDATEtype Action = NoOp | UpdatePerson String

I always like to define an initial action type similar to “no op” where nothing happens. We also define an action that will update the name of the person this todo list is for. Next, let’s write our update function:

update : Action -> String -> String
update action model =
case action of
NoOp ->
model
UpdatePerson newPerson ->
newPerson

We already talked a bit about what this update function does so I’ll skip the step-by-step explanation this time around. Just remember that this will return a modified value based on the initial model state and return a new model state. If we were thinking about this in terms of JavaScript we might write it as (this is just provided as an example, it’s not necessarily the best or only way to write it):

var model = "World";
var action = { type: "UpdatePerson", data: "Foo" };
var update = function(action, model) {
if (action.type === "NoOp") {
return model;
} else if (action.type === "UpdatePerson") {
return action.data;
}
};
update(action, model);

Now we’ll work on our Mailboxes/Addresses/Signal of Model changes.

-- MAILBOXESinbox : Signal.Mailbox Action
inbox =
Signal.mailbox NoOp
modelSignal : Signal String
modelSignal =
Signal.foldp update model inbox.signal

This is a little more complicated. We set up a Mailbox with an initial starting value of our NoOp Action. Mailboxes return a record with the keys address and signal in it. inbox.address gives us the address of the new mailbox we created with the initial value, and inbox.signal gives us the signal of values getting stored in our mailbox. We give our inbox function a type spec of Signal.Mailbox Action so we’re saying our function returns a “Mailbox” type (defined in the Signal package) that stores values of the Action type.

modelSignal is a different beast and introduces a whole new function and concept: Signal.foldp. Let’s look at the type spec first. It takes no arguments, and it returns a Signal of Strings. Signal.foldp is similar to foldl in other languages: it is a fold operation that is dependent on the past value to advance the state. So we fold our inbox.signal (our inbox’s signal of actions) against an initial state of model (currently defined as “World”), calling the update function to advance/modify the value as we progress along that Signal.

Phew. What does it all mean? Let’s take a look at the diagram:

What’s happening? We’re modifying our Signal, which itself is already a value changing over time, based on the previous input. “World” when interacting with an update NoOp “World” call will just return “World”, which gets piped into our next update call.

Updating Our View to Update Our Model

Now we need to update our view function to take an address and set up the button to just update the person’s name (we’ll make this example actually respond to user input, but we’re still trying to stay simple while explaining one of the more complicated parts of understanding Elm).

Please note one thing before we start to look at this code: there ARE shortcuts to what I’m doing below, much in the same way that there are shortcuts to the mailbox/address/etc thing. I’m instead trying to explain what’s really going on behind all of the special functions so that we can truly understand how each part of Elm connects to each other.

-- VIEWview : Signal.Address Action -> String -> Html
view address person =
div [] [
h2 [] [ text ("Todo List For: " ++ person) ],
ul [] [
li [] [ text "Sample Todo Item" ]
],
input [
type' "text",
placeholder "New Todo Item"
] [],
button [
on "click" targetValue (\_ -> Signal.message address (UpdatePerson "Foo"))
] [ text "Add Item" ]
]

The newest bit is inside of the first list argument to our button function. We added this new code:

on "click" targetValue (\_ -> Signal.message address (UpdatePerson "Foo"))

on is a special function that defines an event handler for that DOM element. In our case, we want to define an event handler for the “click” event, and we want to pass in the targetValue (the target of our event handler). The next thing we do is define an anonymous function in Elm. Anonymous functions take the form of:

(\arg1 -> (function body))

In our case, we don’t actually care about the inputs, so we’re using \_ which is Elm’s way of saying “throw out any arguments since we don’t actually need them.”

So, we’re defining an anonymous function that calls the Signal.message function. That function itself takes two arguments: the address to send the message to, and the message to send to that address. In our case, we defined our mailbox to be a mailbox of Actions, and we defined our UpdatePerson action to require a String with it. We can’t write it as:

Signal.message address UpdatePerson "Foo"

Since that would be too many arguments to the Signal.message function, so we need to wrap that UpdatePerson constructor inside of parentheses. So what are we doing here?

When the button is clicked (on the “click” event), call this anonymous function that send an “UpdatePerson” action to our mailbox and give that call an argument of “Foo”.

The Shortcut for Click Events

I won’t leave you all high and dry; there IS a simpler way to do this. I wanted it to be clear what we were really trying to do in that scenario, but thankfully this is such a common operation that there is a much shorter way to implement this:

onClick address (UpdatePerson "Foo")

This takes care of all of that extra trickiness behind the scenes so we don’t have to worry about it. We won’t be using the shortcuts for purposes of this post, but it’s always good to know what options are out there!

Changing Our UI Slightly

Our current model is nice and simple, but it’s not really going to serve us well anymore! Let’s modify it to include a text box for the person’s name.

In the view function, add the following before the textbox input for a new todo list item:

input [
type' "text",
placeholder "Name",
value person,
on "input" targetValue (\text -> Signal.message address (UpdatePerson text))
] [],

Notice we’re using the “on” function again here without any shortcuts, except we’re telling “on” to listen for the “input” event instead. This is why I wanted to avoid the shortcut: there is no easy shortcut for this particular call! Nearly everything else about the call is the same, except we don’t throw away the argument in the anonymous function. We need the text value that has been entered into that text field (targetValue suddenly makes a lot more sense!) and we send the UpdatePerson action with the text passed into that anonymous function. Also, remove the on “click” handler from the button, since we just had that there as a placeholder. Your final view function should look like this:

-- VIEWview : Signal.Address Action -> String -> Html
view address person =
div [] [
h2 [] [ text ("Todo List For: " ++ person) ],
ul [] [
li [] [ text "Sample Todo Item" ]
],
input [
type' "text",
placeholder "Name",
value person,
on "input" targetValue (\text -> Signal.message address (UpdatePerson text))
] [],
input [
type' "text",
placeholder "New Todo Item"
] [],
button [ ] [ text "Add Item" ]
]

Let’s take a look at the whole thing in action now:

Whenever we type anything in our Name text field (where it currently says “World”), we should see the “Todo List For:” bit change to display whatever we’re typing in!

Next Steps

Now that we have a working understanding of Signals, Mailboxes, and Addresses, we’re in great shape to be able to start building something much more complex in Elm! In the next tutorial, we’ll start building a more complicated example of our Todo List that will add/remove items from the list of todos as necessary. To do this, we’ll need to update our model to be of a Record type instead, and we’ll have to refactor a lot of code (and start jumping into using filter functions to modify our list/search through our list/etc).

The final code can be found below:

--

--