A mostly reasonable approach to Polymer and Web Components
- Web Component APIs and attribute names should be consistent with native DOM elements.
- Follow the single-responsibility principle.
- Extend existing elements (e.g. PolymerElements) instead of reimplementing the functionality.
- Fail silently. Components should act like native DOM and should not throw JS errors. Fire an event instead.
- List all dependencies of the web component since the browser will deduplicate multiple requests for the same file.
- Component names must include a dash. The text before the dash is effectively a namespace.
<!-- bad -->
<myComponent></myComponent>
<!-- good -->
<my-component><my-component>
- Component names should be lowercased and dash separated.
Why? Conforms to native DOM element names.
<!-- bad -->
<Awesome-WebComponent></Awesome-WebComponent>
<!-- good -->
<awesome-web-component></awesome-web-component>
- Avoid prefixes shorter than three characters.
<!-- bad -->
<fs-component></fs-component>
<!-- good -->
<familysearch-component></familysearch-component>
- Use attributes to pass data into the web component.
<!-- bad -->
<script>
Polymer({
is: 'familysearch-component',
ready: function() {
this.addEventListener('dataChanged', function(e) {
// do something with the changed data
});
}
});
</script>
<!-- good -->
<familysearch-component data="value"></familysearch-component>
- Attribute names should be lowercased and dash separated.
Why? Conforms to native DOM attribute names. Also produces camelCased property names in the Polymer object.
<!-- bad -->
<familysearch-component myAttribute="value"></familysearch-component>
<!-- good -->
<familysearch-component my-attribute="value"></familysearch-component>
- Boolean values should be based on the existence of the attribute and not its value.
Why? Conforms to native DOM attribute properties (e.g.
hidden
,disabled
).
<!-- bad -->
<familysearch-component boolean-attr="false"></familysearch-component>
<!-- good -->
<familysearch-component boolean-attr></familysearch-component>
- Boolean attributes should not prefix the name with words like "is", "show", or "has" as you would in JavaScript.
Why? Conforms to native DOM attribute property names (e.g.
hidden
,disabled
).
<!-- bad -->
<familysearch-component is-active></familysearch-component>
<!-- good -->
<familysearch-component active></familysearch-component>
- Boolean attributes that remove or disable functionality should be prefixed with the word "no".
Why? This isn't a pattern used in native DOM elements but conforms to a Polymer standard (e.g. paper-button uses
noink
to disable the ripple effect).
<!-- bad -->
<familysearch-component disable-touch></familysearch-component>
<!-- good -->
<familysearch-component no-touch></familysearch-component>
- Fire events to pass data out of the web component.
<!-- bad -->
<familysearch-component>
<!-- don't use bindings to modify a parent elements data -->
<other-component data={{data}}></other-component>
</familysearch-component>
<!-- good -->
<dom-module id="familysearch-component">
<template>
<button on-click="handleClick">Click Me</button>
</template>
<script>
Polymer({
is: 'familysearch-component',
handleClick: function(e, detail) {
this.fire('dataChanged', {clicked: true});
}
});
</script>
</dom-module>
- Event names should have a prefix strongly related to the name of the element. In most cases, the prefix should be the name of the element (e.g.
familysearch-component-change
).
Why? Not only will they be uniquely namespaced, but if you use any one of these event names, the event will not propagate through the shadow DOM.
// bad
this.fire('error', new Error());
// good
this.fire('familysearch-component-error', new Error());
- Event names should end in a base form verb or a noun.
// bad
this.fire('familysearch-component-data-changed', {});
// good
this.fire('familysearch-component-data-change', {});
this.fire('familysearch-component-upload-success', {});
- Use
this.listen()
instead ofthis.addEventListener()
.
Why? Works cross platform and provides an
unlisten()
function to unsubscribe from the event.
<!-- bad -->
<dom-module id="familysearch-component">
<template>
<button id="myButton">Click Me</button>
</template>
<script>
Polymer({
is: 'familysearch-component',
ready: function() {
this.$.myButton.addEventListener('click', function(e) {
console.log('tapped');
});
}
});
</script>
</dom-module>
<!-- good -->
<dom-module id="familysearch-component">
<template>
<button id="myButton">Click Me</button>
</template>
<script>
Polymer({
is: 'familysearch-component',
ready: function() {
this.listen(this.$.myButton, 'tap', 'handleTap');
},
handleTap: function() {
console.log('tapped');
}
});
</script>
</dom-module>
- Favor declarative event handlers over JS event handlers (e.g. use the
on-tap
attribute instead of using thelisteners
property orthis.listen()
).
<!-- bad -->
<dom-module id="familysearch-component">
<template>
<button>Click Me</button>
</template>
<script>
Polymer({
is: 'familysearch-component',
listeners: {
tap: 'handleTap'
},
created: function() {
this.listen(this, 'tap', 'handleTap');
}
handleTap: function(e, detail) {
console.log('tapped');
},
});
</script>
</dom-module>
<!-- good -->
<dom-module id="familysearch-component">
<template>
<button on-tap="handleTap">Click Me</button>
</template>
<script>
Polymer({
is: 'familysearch-component',
handleTap: function(e, detail) {
console.log('tapped');
}
});
</script>
</dom-module>
- Property names should be camelCased.
Why? Produces lowercased, dashed separated attribute names in the DOM.
<!-- bad -->
<dom-module id="familysearch-component">
<script>
Polymer({
is: 'familysearch-component',
properties: {
'my-var': String
}
});
</script>
</dom-module>
<!-- good -->
<dom-module id="familysearch-component">
<script>
Polymer({
is: 'familysearch-component',
properties: {
myVar: String
}
});
</script>
</dom-module>
- Private properties should be prefixed with an underscore.
<!-- bad -->
<dom-module id="familysearch-component">
<script>
Polymer({
is: 'familysearch-component',
properties: {
private: String
}
});
</script>
</dom-module>
<!-- good -->
<dom-module id="familysearch-component">
<script>
Polymer({
is: 'familysearch-component',
properties: {
_private: String
}
});
</script>
</dom-module>
- Define constants outside of the Polymer constructor.
<!-- bad -->
<dom-module id="familysearch-component">
<script>
Polymer({
is: 'familysearch-component',
properties: {
CONST: {
type: String,
value: 'value'
}
}
});
</script>
</dom-module>
<!-- good -->
<dom-module id="familysearch-component">
<script>
var CONST = 'value';
Polymer({
is: 'familysearch-component',
});
</script>
</dom-module>
- Wrap the contents of the script tag inside an immediately-invoked function expression (IIFE).
Why? To prevent global variables from being created since the script tag is run in the
window
scope.
<!-- bad -->
<dom-module id="familysearch-component">
<script>
var isNowGlobal = true;
</script>
</dom-module>
<!-- good -->
<dom-module id="familysearch-component">
<script>
(function() {
var isNowGlobal = false;
})();
</script>
</dom-module>
- Method names should be camelCased.
<!-- bad -->
<dom-module id="familysearch-component">
<script>
Polymer({
is: 'familysearch-component',
'my-method': function() {
// ...
}
});
</script>
</dom-module>
<!-- good -->
<dom-module id="familysearch-component">
<script>
Polymer({
is: 'familysearch-component',
myMethod: function() {
// ...
}
});
</script>
</dom-module>
- Private methods should be prefixed with an underscore.
<!-- bad -->
<dom-module id="familysearch-component">
<script>
Polymer({
is: 'familysearch-component',
privateMethod: function() {
// ...
}
});
</script>
</dom-module>
<!-- good -->
<dom-module id="familysearch-component">
<script>
Polymer({
is: 'familysearch-component',
_privateMethod: function() {
// ...
}
});
</script>
</dom-module>
- Use
dom-if
to conditionally render large portions of the DOM or DOM that will not toggle between hidden/shown states. - Use the
hidden$=
attribute to conditionally render small portions of the DOM or DOM that will toggle between hidden/shown states. - Favor single bindings
[[ ]]
over double bindings{{ }}
.
- Use the Polymer Style Guide documentation
- Follow web component accessibility best practices.
- use ARIA roles when necessary, test for accessibility.