Polymer anti-patterns: Prevent leaking internals between components

Data binding and event bubbling might couple your components much closer than you expect.

Ronny Roeller
Jul 10, 2017 · 3 min read
How decoupled are your components?

Lately, we refactored two larger Polymer applications. These refactorings turned out to be considerable more time-consuming than expected. Hence, we went back to understand what caused the unforeseen complications.

Here are the two anti-patterns that we identified:

Anti-pattern 1: Leak data structure via data binding

The problem

We wired up the mini apps by directly passing e.g. the current user:

<mini-app user="[[user]]"></mini-app>

And then naively used the passed user within the mini app, e.g.:

<div>Hello [[user.name.givenName]]!</div>

The problems started when we simplified the data model in our application: For example: Storing the given name in user.given_name instead of user.name.givenName required to adjust every single mini app, all physically “decoupled” in separated Github repositories.

Solution

For example, our mini app might require the user property to be in a format like this example:

{
firstName: 'Joe',
email: 'joe@company.com'
}

The calling component has to create then the required data on the fly, e.g.:

<mini-app user="[[_convert(user)]]"></mini-app>...convert(user) {
return {
firstName: user.given_name,
email: user.primary_email,
}
;
}

Now, whenever the data model of the application changes, we only need to adjust the convert methods. No knowledge about the inner workings of the mini app is required.

As a positive side effect, we also avoid leaking additional data points that the mini app shouldn’t know about (e.g. the security role might be stored in the user object).

Anti-pattern 2: Leak event structure via event bubbling

The problem

this.dispatchEvent(new CustomEvent('closed', {
bubbles: true,
composed: true,
}));

The embedding application would listen for these events, and change e.g. the view:

ready() {
super.ready();
this.addEventListener('closed', e => this._toAppSelection(e));
}

It felt like an elegant solution: we have one central part that listen to closed events, and then anything within any mini app can trigger the central code.

But what we had really done was introducing a dependency from the mini app to the embedding component: The mini app knew about the magic closed event within the embedding application. So, when we wanted to refactor an event in the embedding application, we had to immediately reflect the change in every single mini app!

The solution

For illustration, the mini app fires now a finalized event when it’s done, which gets translated by a mini app-specific wrapper:

<mini-app on-finalized="onFinalized"></mini-app>...class MiniAppWrapper extends Polymer.Element {  onFinalized(e) {
this.dispatchEvent(new CustomEvent('closed', {
bubbles: true,
composed: true,
}));
}
...

If we now want to refactor the events in the application, we only need to adjust all the wrappers — instead of touching the mini apps itself.

Happy coding!

Want to learn more about Polymer? Have a look to all our Medium posts on Polymer.


NEXT Engineering

Tech lessons learned while making innovation smart, simple and sticky.

Ronny Roeller

Written by

CTO at @Next. Building agile SaaS platform to make innovation smart, simple and sticky. @stanforddschool @INSEAD

NEXT Engineering

Tech lessons learned while making innovation smart, simple and sticky.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade