Cliff Notes Version:
Managing State w Web Components
Here’s what I settled on, stripped of the rationale, which is below:
- Avoid 2-way binding, even if Polymer templates provide for this capability.
- Propagate state via “properties down, events up”, whenever the specific state is simple enough for this to work easily.
- Use polymer-redux (or redux or flux or mobx or … ) to manage state in all other circumstances.
Hopefully, article will save you some time, if you’re just entering this world. I’ll detail where I spent my time, below.
Weird meets Thought
Web components are already weird for the front end developer marketplace, so if you add the idea of having to think about your design re: state rather than having your framework impose some thinking on you, that can slow you down. It did me.
Managing state quickly became an issue for me, as I was learning Polymer and/or web components basics.
I’m still a first year JS coder – nevermind the 3+ decades of writing abandon-ware in over a dozen previously popular platforms, from APL to Java Swing or even Oracle ADF.
Managing state was one of three areas that has, so far, consumed most of my time learning how to write an an app in web components, or in this case, Polymer. What follows is how I actually had to learn to think about it.
Rationale for the 3 rules:
You won’t find the 3 rules reflected in all of my github thought experiments, because it took me a lot of trial and error, and time on the Polymer Slack feed, to divine some of these conclusions. I had to do it the hard way, first, often in several different variations.
Also, see Credits below.
1. Avoid 2-Bay Binding
First, the bad news. If you’re coming in from the cold and Polymer has a reputation for being too hard, some of it is from this odd combination of capabilities. Too clever by half, as some might say. The data system, with 2 way binding, helper methods, and careful techniques that work in certain situations and not others, is really pretty amazing, but only if you have the patience to wade through it. The edge cases can be a MF, but I must have started with the edge cases, because it confused me like crazy.
Bottom line is that even before lit-html is considered, using 2-way-binding can be a little like making a deal with the devil. Sometimes it works easily, other times not, and it fails the being obvious test more often than not. So there’s that.
PRO TIP: This ‘Data flow examples’ section helped me the most, when learning how the data system works.
As if to put the nail in the coffin on 2-way binding, Polymer3 offers, but does not require, lit-html as an alternative to it’s initial templating piece. Many of us, myself included, have come to regard this option as the go-to plan for migrating our apps to Polymer3.
And lit-html doesn’t do 2-way binding. So that conclusion to avoid 2-way from above? It becomes the only option, if you go with lit-html. Nail in the coffin.
The exception that proves the rule?
Benny Powers points out that there are actually some places where 2 way binding works out swell. Like for example, this, from the slack feed:
If I'm buillding a reusable element, and it contains an input, and i won't ever need to track the internal state of the element between it and it's input (e.g. i'm exposing that input's value), then IMHO there's no reason not to use declarative two-way binding inside my shadow dom. I'm starting to move from this "compose shadow dom into apps" approach to exploring a "use shadow dom only for reusable components and write app code in the light dom" approach so that fits in as well.
What makes this so instructive is the level of detail required to even describe it properly. So as with all things, the ‘never 2 way’ rule only applies to the beginner/intermediate dev, such as myself. Once you get to the black belt level, you can break such rules with abandon, and still be just fine. Just so long as the beginner/intermediate coder isn’t responsible for maintaining your code, later, when you have moved on.
2. Properties down, events up
So I’m studying all this stuff and watching the various published talks and monitoring the slack feed and it still isn’t obvious, because everyone seems so agreeable about everything. There are just so many options, so many ways of managing state. I don’t want many ways, I just need to get my job done! So here’s how I settled on properties down, events up, as the default.
A whiner might say “gosh they made me read all this documentation on my options and never told me to just default to properties up and events down until that doesn’t work.” Because that’s the main thing you need to know. Everything else can just confuse you, initially. Or it did me.
Go to work. Write your app. Follow this simple principle, until it fails you, then you can read the kajillion of words of documentation. In that order.
At last, we’re writing our app instead of combing through endless docs and slack chatter. See? This web components stuff isn’t so bad after all.
But there is this one area. In my case it was the workflow/navigation/content-hiding kind of use case.
For you, it could be something else.
“Sure,” you say to yourself “I could do this with properties down and events up, but it’s starting to feel a bit awkward.” Like way too brittle, and maybe it shouldn’t be. What to look for? Usually it’s when following the nested components up and down gets really hard to manage, properties wise. Again, your mileage may vary.
This is where Redux, or Mobx or … comes into the mix. A state management system. I tried designing my own using a global window namespace, that was the dumbest thing I ever did since the previous dumb thing. Main thing is you don’t have to re-invent this wheel, it’s there for you to use, and it’s mature.
I chose Redux specifically, for three reasons.
- It seems to be the darling, in all the talks. Others are rarely even mentioned.
- It is more explicit or verbose than Mobx, which is more my style of coding.
- polymer-redux is an already mature web component, so I didn’t have to re-invent that, either.
It still took me a while to learn, but mostly that was because I kept trying to make it harder than it already was.
PLEASE NOTE: Everyone will tell you the same thing – ignore this at your own peril – don’t default to [redux] for all shared state, just use it where it is worth the extra complexity.
Too many helpful talks, slack posts, and doc writers to all credit here, but special thanks to Benny Powers and Daniel Bustowicz for the hardest part, which was nailing it all down into one concise set of thought processes. Without that, much of my clarity on these issues would never have been possible.