Rails-like Flash Messages in Elm

Subscriptions to the rescue

billperegoy
im-becoming-functional
9 min readMar 26, 2017

--

The Problem

I’m working on a project using Elixir and Elm that needs real-time interaction between multiple users. I’ll be using Phoenix channels to send real-time information from server to client. I want part of this information to be displayed as small banners on each user’s page. These are transient messages that should be displayed for some set period of time and then go away. In many ways, it’s a lot like the Rails flash. I want to take this one step further and allow a number of flash messages to be displayed at once, and have each message timing out after the allocated time. So you might see something like this on your page.

These alerts will display as they come in and timeout after a selected time period so users will know what is going on with users they interact with.

Given that we have elements that need to change asynchronously and are related to wall-time, this looks like a perfect opportunity to use Elm subscriptions. We will start with a simple, inelegant, brute-force method and later attempt to make more sophisticated use of subscriptions.

Building the Model

Our data model is pretty simple. We need a record that represents a single flash element and a list that can represent a number of flash elements that can be displayed.

Note that each element contains an integer id, text and color information as well as an expiration time. This time represents the absolute time where the element should vanish.

In the main model, we store a list of these elements, along with the next available id and the current time. This current time will be used to compare against the element’s expiration time.

Setting up Subscriptions

In order to use this model, we will need two subscriptions.

The first of these subscriptions will just update the current time in the model. The second will trigger an action that walks through the flash elements and timeout those that have passed their expiration time. Note that I’ve chosen to trigger each of these subscriptions once per second.

Update Function

This is all pretty simple. The first action is invoked every second by the subscription and simply places the current wall time into the model.

The second action creates a new element given text, color and duration. It adds this element to the head of the element list in the model and increments the next id.

Finally, the last action is used to remove any flash elements from the list if they are past their expiration time. Again it’s really simple. We use a list filter to only keep the elements that have not expired. This action is also called once per second from the subscriptions.

The View

In order to see this, we need a simple view. This view assumes we’ve loaded bootstrap to give us some basic CSS styles.

This view just maps over the flash elements and displays them in a div that uses bootstrap styles to give them a flash-like appearance.

You can find a working example of this code along with a form to create new flash elements in this github release.

Critiquing this Approach

So what do I think about this approach? First of all, it’s relatively simple and completely functional. It does exactly what I intended. But one thing completely bugs me. When I think of an Elm application, I think of reactive programs. So I might expect that when a flash element was created, it also spawned a subscription that would fire at the time the element was due to expire. This subscription event would call an action that deleted that element.

Instead, we are using a more brute force approach that calls a timeout routine once per second. This routine effectively polls each element and asks it whether its time to live had expired. This feels very imperative and essentially not very Elm-like. I’m going to next see if we can refine this application to take a more reactive approach.

Going Reactive

My first thoughts when thinking about this problem in a reactive way would be to dynamically create the list of subscriptions. Each time we added an element, we’d add a subscription that would fire sometime in the future when that element was due to expire. That event would also ideally cause the triggering subscription to be deleted. This gets us out of the polling approach we are using and would greatly reduce the number of events being processed by the Elm runtime.

So, given the current Elm architecture, how can we move to this approach? At first glance I wasn’t sure it was possible, but I was encouraged by this statement in the Elm documentation.

So, I should be able to take something out of the model (possibly the list of flash elements) and dynamically convert that to a list of subscriptions. I couldn’t find any examples of this online, so I started trying some experiments.

Making a Subscription to Remove a Single Flash Element

In order to get around this polling issue, we need to first be able to create a subscription that will target a particular element and delete it after a defined time. I cobbled together a hardcoded example like this.

First I defined a new subscription that targeted a fixed flash element.

This creates a subscription that will trigger an event after 60 seconds. Of course this will also trigger the same event every 60 seconds after that (which isn’t exactly what we want, but it’s a start).

We then add an update function clause like this.

If you try this out after creating an element with id of 0, you’ll see that this does work. Now, how can we generalize this and have subscriptions like this added to the subscription list each time we create a new element?

Moving Subscriptions into the Model

Given that we pass the model into the subscriptions function, I’m hoping that we can use the list of flash elements in the model to dynamically create subscriptions to add to the existing list. I wasn’t able to find any examples of this online, so I’ll walk through some of the experimenting I did to attempt this feat.

The first step is to move the list of subscriptions into the model and reference that part of the model in the subscriptions function.

We also have to update the init function to add the default subscriptions we had previously.

This makes no functional changes to the app but now that the subscription information is in the model, we can dynamically add new subscriptions to this list as we create new elements. We will attempt to do that next.

Dynamically Adding Subscription Events

The first step to being able to dynamically add subscription events is to define a function that will create a subscription that will delete a particular element.

This function accepts an id and duration and returns a subscription. We can then replace the subscription called out in the init function with a function call.

This works just as it did previously but we still have the hardcoded id. Let’s now attempt to build this list dynamically.

First we remove this hardcoded subscription from the init function.

Next we modify the update action that creates a new flash element to also add a new element to the subscription list.

We have appended a new subscription element to the front of the subscriptions list. Note that we no longer need to store expirationTime in the element any more. For now we just set this to zero.

On the surface, this now seems to work. I can add elements and they time out and go away after some time. We will explore the details later. But first let’s clean up the code a bit.

Cleaning Up

Modifying the code to use dynamically generated subscriptions eliminated the need to store expirationTime in the flash element. It also eliminates the need to store currentTime in the model. This allows us to clean up the model significantly. In addition, we can remove the TimeoutFlashElements action can be removed completely.

The new simpler model now looks like this.

Also our list of Msg types reduces to this.

You can find a working copy of this code along with some forms to create elements in this git release.

Summary

One thing I don’t like about this solution is that each time I create a new element, I create a subscription that lasts forever, long after the element it targets has gone away. I’d love it if Elm offered another function in the Time library that allowed a one shot subscription event at an absolute time. I could see this working.

This would create a new at function that would fire once at an absolute time. This could be provided at the time the element was created by adding the desired duration to the current time.

I think I may do some work to update the application to store each subscription in a tuple with the associated id. I could then delete the subscription as I delete the associated flash element.

Postscript

I’ve been back and forth on whether these subscriptions work as expected. At times they seem to work perfectly, but at other times the flash elements go away earlier than expected. While I’m not sure why things seem so inconsistent, I suspect that some of the problem is related to the way Time.every works. When you create a new “every” event, I sometimes think that the “every period” cycle starts at the time the application was started instead of the time the subscription was added. If anyone has any knowledge of how this works internally in Elm, please get in touch.

--

--

billperegoy
im-becoming-functional

Polyglot programmer exploring the possibilities of functional programming.