Passive State Management

Yeah, I know it has been a few months. I will try to get these out more regularly now that I have actually worked on a few things worth writing about.

When working with client-side-rendered libraries or frameworks such as Angular, React or Vue, it is reassuring to know that each of these popular tools provides a nice way to handle the more complicated parts of application state in a civilised manner:

  • Angular has quite a few candidates, the most common being NGRX
  • React comes with Facebook’s personal flavour of Redux
  • Vue has its own library called Vuex

All of these are based on the premise that state is managed client-side within the app and provide a means to declaratively control the handling and mutation of the app state. They serve as a single read-only source of truth for the myriad of components that derive their behaviour from the app state. For the sake of this post’s title, let’s call this active state management.

However, far out in the untended fields of legacy applications and frontends, there is a problem to be found. Many companies, and not just small ones, are stuck with a load-bearing, revenue-generating, business critical application that is too expensive to replace in a sane manner (at least from a business perspective). So they are actually, truly stuck with it for the foreseeable future.

Of course, after the initial tears have dried, a hesitant hand will come up in the back and somebody will ask:

If we cannot get rid of it, can we build around it?

This usually gets all the developers at the table very excited, because it means they get to argue for their favourite tool or framework that gets the job done. Which is fine with me, truly. But once the dust settles, there are some issues.

If you are able to keep state centrally within whatever tech you use to wrap that legacy app, you are alright. Alright as in, you are still stuck with legacy apps, but you can work around that in a civilised manner.

However, there are scenarios where you are unable to do so. An example could be a navigation layout that is rendered server-side and served via a simple embed. Or maybe you are working with modals extensively, but the modal content is both externally embedded and has impact on the app state.

And as soon as that happens, you are stuck with a problem. While you may have a centrally managed state within your wrapping client-side app, that state may have to change based on external factors outside of your app’s context. Again, if you are able to solve this in a civilised manner, please do. If not, allow me to introduce you to one of the most underused APIs on the web, which will come in handy to allow passive state management, where state may change based on external factors.

The MutationObserver API is truly useful when keeping track of DOM changes, doubly so if you need to update your app state in response to changes to the DOM that occur outside of your app context. As usual, Mozilla’s documentation is more than up to the task of getting you up to speed, and all current browsers are implementing this API for your engineering pleasure.

In a nutshell, you create an Observer on a DOM node of your choosing. For performance reasons, it might be a good idea to be as selective as you possibly can when configuring your Observer, going as deep into the DOM tree as you can. Further configuration options include listening only to specific changes such as attribute values.

Once attached to a DOM node, the Observer will watch that node and, depending on your configuration, all its child nodes. A callback function is executed every time a change matching your parameters is observed.

What you are given to work with is a list of MutationRecords, which are individual changes to whichever properties and nodes you are observing. Depending on your Observer’s configuration, you can glean a wealth of information from such a record, including both former and current state of attributes, values or child node data.

This mechanism allows watching for DOM changes that happen outside of the app boundaries. And when there are changes that your app might be interested in, you can update the state using your app’s internal mechanism, like a civilised person. And voila, your legacy application can be wrapped until it is feasible to replace it entirely.

Dev team is happy. Business people are happy. Everbody wins.