Architecture Statecharts

Constructing robust Ember.js UIs with Statecharts

Michael Klein

· 8 min read

Ambitious applications are full of complex user flows whose implementation effort is often hard to estimate. The reason for this often lies in the hidden complexity of parts of flows that are only rudimentary contrived by the people that came up with them. Statecharts are a tool that has been used since the 1980s to model complex reactive systems successfully and can help designers and developers drive construction and implementation of robust application flows collectively.

Here at EffectiveEmber we are big believers in modeling our application flows explicitly with statecharts and we want to share our enthusiasm about this pattern with the rest of the Ember.js community. Statecharts are in our experience a perfect supporting tool for helping teams implement ambitious user flows with Ember.js so to dive into this topic let’s imagine a scenario that most developers will be familiar with:

Someone responsible for coming up with new features of the product you are working on comes up to you and requests to build a new feature that they imagined. In this post's example, we will assume that they ask you to build a fancy™ select input component.

They don’t come to you with a specification about this new feature but show you a mockup of how they imagine this feature to work:

Static mockup of a custom select input to select countries
Static mockup of a custom select input to select a country

Right away people that have been developing web applications professionally will realize that this feature entails a lot and will take some time to get right but from the get-go, we run into one of the biggest problems in modern application development - static mockups.

At first, this feature masks itself as being pretty simple. It’s basically a custom input that will open a dropdown when being clicked on and then countries appear that the users can select from.

We'll create a quick html stub with TailwindCSS to get an idea on how we can implement this feature:

  • United States
  • United Kingdom
  • United Arab Emirates

Right away we notice that a lot of things are unclear just from looking at the static mockup that the product team asked us to implement. Questions that start with “What happens when…” and “Can the users do… when …” come to mind quickly when beginning to plan how this feature can be implemented.

Static mockups vs behavior in a reactive system

In our opinion, this is one of the biggest challenges when developing ambitious applications today. Requirements are mostly passed around as static mockups but the applications and components that we are asked to build aren’t static at all. They enable user flows via a dynamic interface that continuously needs to react to internal (e.g. a user clicking a button) and external events (e.g. data arriving via a web-socket). We are not building an application made of static pages but complex reactive systems that need to react to events continuously.

The designs that we usually receive don’t cover the reactivity well unfortunately and that’s exactly why so many requirements are left unidentified before implementation begins. There’s not a clear way to communicate to developers the intended behavior. A static mockup leaves too many things undefined and sometimes designers simply forget certain interaction patterns. Just as an example it is quite easy to forget that loading data from a backend API will take some time and might even fail in some cases when you don’t implement asynchronous API calls as your day to day job.

This isn’t a new phenomenon though - developers decades ago were running into this issue before us and they found a way to model these systems successfully by describing their dynamic behavior as a set of states and events (Harel, 1987).

A shared visual language

In his 1987 published paper Statecharts: a visual formalism for complex systems David Harel proposes statecharts - basically state transition diagrams with the addition of hierarchy, concurrency and communication - as a tool to model complex reactive systems (Harel, 1987). This formalism has worked well for other parts of the software engineering industry to model complex systems (Harel, 2007; Benowitz et al., 2007) and with ember-statecharts and the underlying XState -library we can make use of this paradigm in our Ember.js applications today.

We can start out modeling the behavior of our select component based on the static mockup that we have. For modeling, we will make use of the XState-visualizer - a visual tool to interact with the statechart we are modeling. You can click on events to transition between states and see an interactive result of your modeling.

The visualizer gives us a tool at hand that designers and developers can use collaboratively to define the behavior of the component we are trying to build explicitly. Later on, the developers will be able to take this modeled statechart configuration and use it as the foundation for implementing an Ember.js component.

Modeling behavior

When using statecharts as the foundation for implementing user flows we don’t jump into implementation right away. We will first model the behavior visually and when that’s done hook it up with our application codebase.

We start very simply. We have two states - open and closed and we will start in the closed state. When the user decides to TOGGLE the select input - that’s when the dropdown should appear - we transition into the open-state. Of course, when we decide to TOGGLE again the dropdown will close.

Refining behavior

Toggling a boolean value isn’t that interesting but as discussed our custom select component is more complicated than that. But before getting into more interesting things like async filtering of selectable items we’ll fix a small usability issue.

Handling outside clicks

Right now we only toggle the dropdown in both the open- and closed-state but when the dropdown is opened and the user clicks outside the dropdown UI we want to close the dropdown. This is behavior that only should be triggered when the dropdown is open so we will add a new event - CLOSE:

Typeahead functionality

As indicated in the static mockup for our select component we want to be able to search a list of many countries. To make this easier on our users we want to provide a typeahead functionality. This means that based on what the user inputs in the input field the list of selectable countries should be filtered by.

This behavior complicates the component quite a bit. We will need to keep track of a filter-property that we can use to filter the name of the countries that we are selecting from by and we need to create a new filtered property for the countries that we want to display.

We will use XState's context feature to keep track of the filter. The filtered countries will be a reactive getter on the component itself.

The product designers in our project also tell us that they want to debounce the filtering of the results while the user types. They feel that updating the list while the user types continuously will be distracting and they don’t want to see the list jumping all the time.

To make this work we will need to extend the existing open state of the statechart a bit. open now itself can be in two different states. We can either be filtering - while the user types - or be filtered when we applied the filter that the user put in. Thinking of these different conditions that the component can find itself in, as states, is very powerful. First of all, we now have names for what the component is doing which makes discussion about the behavior much easier with other stakeholders but it also makes it easy to realize that most likely we want to indicate the state of filtering to the users somehow while they type. This is something that is easily forgotten when modeling component behavior implicitly without relying on states.

We could use ember-concurrency for debounce functionality like this but XState’s send and cancel functionality got us covered so we will stick to only using XState for this component:

To make debouncing work we will send a FILTER event every time the user inputs something into the input field. Whenever we receive the FILTER-event we will also send the APPLY_FILTER-event to ourself that updates the filter-property with the current input value. For debounce purposes, we will delay this sending of the APPLY_FILTER event for a specified debounce timeout of 300ms. Whenever we receive a new FILTER-event we will cancel the previous sending of APPLY_FILTER and start over again - in essence debouncing the setting of the filter-property until the user stops typing for 300ms.

Implementation

The modeled statechart alone gets us a long way to implement our select box with Ember.js - we can copy the XState machine-configuration we created into our app and use ember-statecharts to use this machine to drive our select component:

Without creating any conditionals we can open and close our dropdown as expected. We don’t need to add any logic to the toggle-action of the component anymore - the behavior is modeled in the statechart explicitly.

The same goes for the debounced filtering. We modeled this behavior explicitly in the statechart so the only thing for the component to do is to forward the FILTER-event and add the input’s value to the event-data we send with the event.

This is the main change in your programming model when working with statecharts in Ember.js - you don’t implement logic in your actions anymore you simply forward events to the statechart and the statechart decides what happens next.

The nice thing about this is that you don’t have to keep any implicit states in your head anymore when reasoning about code because you modeled all existing states and the transitions between them. When a state doesn’t understand an event that you are forwarding to it nothing happens - it has become impossible to trigger unexpected behavior in your applications.

Updating the UI based on the state of the statechart is easy as well. We can use the matchesState-decorator that ember-statecharts ships with that creates a property that updates when the statechart transitions between states. And when we want to access the statechart’s context we have access to it via statechart.state.context.

All of this combined leaves us in a great state. We can be confident about what our component is doing and we don’t need to worry about unexpected behaviors being triggered because we forgot to check if the dropdown was open before trying to filter the dropdown results or similar issues that often arise when not modeling all available states explicitly.

A feature request

We are happy with the way our component behaves now. After trying out the new feature the product team has a feature request though. They want to be able to use the keyboard to navigate the list of countries.

When modeling this with a statechart implementation of this functionality gets very simple. We just need to think about which states this feature applies to and what event we will need to send.

We decide that this feature only applies to the open.filtered-state and that we will have multiple keyboard shortcuts available.

  1. we want to be able to move up and down by pressing the up- and down arrow-key
  2. when pressing enter the currently selected country will be selected - in our case this just means closing the dropdown

We jump into the XState-visualizer and add the DOWN, UP and ENTER events and add the selectedIndex-property to the statechart’s context to keep track of the currently selected item in the dropdown:

Adding this new functionality is simple when using a statechart. We remember the selectedIndex that starts out at 0 and when the UP or DOWN events are received we update the index accordingly. Obviously we only want to do that for UP when there are countries further up selectable and vice versa. To do this properly we will guard the transition with a condition and use ember-statecharts-withConfig-hook to only allow the transitions when it makes sense to move up or down based on the countries we have available in the dropdown and the current selectedIndex.

The ENTER event is also very straight forward. In our case we will simply transition into the closed-state when there’s a selectable country available. In an actual production application we could trigger some kind of onSelection-action that we pass in from the outside while we transition to closed.

To hook up the new behavior in our component we add the keyboard shortcuts with ember-keyboard 's on-key-element-modifier and have the triggered actions send the UP-, DOWN- and ENTER-events to the statechart. No other change is required in the component itself because the entire logic is modeled in the statechart.

Summary

This post gave you a glimpse into how statecharts can be used to implement robust UIs in Ember.js by modeling your behavior explicitly instead of relying on implicit states based on conditionals in your code. Refactorings become simple, you are thinking about behavior much more deeply before jumping into implementation and you are even documenting your code with a visual language that you can use to discuss the behavior with non-technical stakeholders of your project in a meaningful way.

We are aware that the component we developed throughout this blog post isn’t a production-ready typeahead implementation. Certainly, we’d like to be able to pass in data from the outside and maybe the product team would like to be able to clear the input by pressing the ESC-key at some point. But by modeling the entire behavior with a statechart we have a great starting point for an ambitious typeahead component. You can already imagine how we’d handle new requirements like the ability to clear the input - we won’t need to do much more than add a new event and send that event to the statechart when the user wants to clear the field. When you have ideas on how our fancy select component can be improved let us know - we’d be happy to do a follow-up blog post on extending the existing behavior in the future.

This post barely scratches the surface of what you can do when modeling your component and application behavior explicitly with statecharts. The main takeaway that we want to bring across is that it pays huge dividends when modeling user flows in your application explicitly:

  1. With statecharts, you finally have a visual language that you can use to discuss behavior in your applications with other stakeholders - the XState-visualizer gives you a powerful tool at hand that you can use to work out user flows with designers and developers collaboratively.
  2. Implicit user flows that can only be assumed from static mockups surface while modeling the statechart and you can plan ahead before jumping into implementation right away.
  3. It becomes impossible to trigger unexpected states in your application because all of the things that can happen are modeled. When you send an event to a statechart that the current state doesn’t understand nothing happens.
  4. Refactoring becomes straight forward. We can add events to states to trigger new behavior or nest existing states deeper. All of this doesn’t change the behavior we modeled before. You can add new features on top of existing ones with confidence and don’t need to worry that you break existing behavior when adding new features.

We are very excited about the possibilities that statecharts open up when developing applications with Ember.js and plan to post more on this topic in the future on this blog. In the meantime, we want to encourage you to give the documentation pages of ember-statecharts and XState a thorough read to see what statecharts can do for your application architecture.

We have been using statecharts in complex application architecture for several years now - if you face challenges implementing complex application behavior yourself we are here to help you and your team deliver ambitious applications with Ember.js. Don’t hesitate to - we’re always excited to learn more about the things you are building and the challenges you face while doing so.

References

  1. David Harel (1987). Statecharts: a visual formalism for complex systems. Science of Computer Programming 8, 231 - 274
  2. David Harel (2007). Statecharts in the Making: A Personal Account. HOPL III: Proceedings of the third ACM SIGPLAN conference on History of programming languages
  3. Ed Benowitz, Ken Clark, Garth Watney (2006). Auto-coding UML Statecharts for Flight Software. 2nd IEEE International Conference on Space Mission Challenges for Information Technology (SMC-IT’06)

Schedule a call

We are here to enable your team to deliver ambitious applications. Let's discuss how we can help.

European flag

© 2023 effective ember All rights reserved.