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:
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:
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.
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).
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.
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 -
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.
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.
Right now we only toggle the dropdown in both the
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 -
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
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
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
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
for debounce functionality like this but XState’s
cancel functionality got us covered so we will stick to only using XState for
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
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.
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
we send with the
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
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
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.
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.
We jump into the XState-visualizer and add the
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
that starts out at
0 and when the
DOWN events are received we
update the index accordingly. Obviously we only want to do that for
there are countries further up selectable and vice versa. To do this properly
guard the transition with a condition
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
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
we pass in from the outside while we transition to
To hook up the new behavior in our component we add the keyboard shortcuts with
on-key-element-modifier and have the triggered actions send the
ENTER-events to the statechart. No other change is required in the component itself because the entire logic is modeled in the statechart.
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:
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.
We are here to enable your team to deliver ambitious applications. Let's discuss how we can help.