Home Start Get started Quick tour of Polymer Install the Release Candidate Polymer Feature overview What's new About Polymer 2.0 Upgrade guide Hybrid elements Release notes Custom elements Custom element concepts Define an element Declare properties Shadow DOM & styling Shadow DOM concepts DOM templating Style shadow DOM Custom CSS properties Events Handle and fire events Gesture events Data system Data system concepts Work with object and array data Observers and computed properties Data binding Helper elements Tools Tools overview Polymer CLI Document your elements Test your elements Optimize for production Advanced tools Services polymer.json specification Node support Resources Browser compatibility Glossary API Reference API Reference App Toolbox What's in the box? Using the Toolbox App templates Responsive app layout Routing Localization App storage Service worker Serve your app Case study Shop News Blog Community Feature overview
What's new
About Polymer 2.0 Upgrade guide Hybrid elements Release notes
Custom elements
Custom element concepts Define an element Declare properties
Shadow DOM & styling
Shadow DOM concepts DOM templating Style shadow DOM Custom CSS properties
Events
Handle and fire events Gesture events
Data system
Data system concepts Work with object and array data Observers and computed properties Data binding Helper elements
Tools
Tools overview Polymer CLI Document your elements Test your elements Optimize for production Advanced tools Services polymer.json specification Node support
Resources
Browser compatibility Glossary
API Reference
API Reference

Polymer 2.0 includes a number of breaking changes. However, the Polymer team has worked to provide an incremental path for updating Polymer 1.x elements to 2.0.

Polymer 2.0 supports several types of elements:

  • 2.x class-based elements use the ES6 class-based syntax supported by the custom elements v1 specification. Recommended for new development in Polymer 2.0.
  • 2.x legacy elements use the Polymer factory method, and have most of the 1.0 APIs available to them, as well as any new 2.x APIs.
  • 2.x hybrid elements are elements defined using the legacy Polymer factory method, with extra code for backwards compatibility with 1.x. They can run on Polymer 1.7+ as well as Polymer 2.x. As much as possible, hybrid elements should stick to the common subset of APIs supported by 1.x and 2.x. In some cases, they may need to conditionalize code to run in 1.x or 2.x. Maintaining hybrid elements is more complicated than maintaining class-based or legacy elements, since they need to be tested on both 1.x and 2.x.

When porting a large project, you can update to Polymer 1.7 and upgrade elements to 2.0 hybrid style one at a time. After all of the elements have been upgraded, you can test your project against Polymer 2.0.

The Polymer team plans to release a Polymer Upgrade tool to automatically perform a number of the changes required to upgrade 1.x elements to either hybrid or class-based style. The remaining changes require either manual code changes, testing, or both to ensure that your element operates the same in 2.0 as it did in 1.x. The upgrade tool is not available at this time.

See Install Polymer 2.0 for installation instructions.

This upgrade guide is a work in progress. Please report issues on GitHub.

When upgrading an element or app, there are several possible scenarios:

  • Upgrading an individual element.
  • Upgrading a small application.
  • Upgrading a large application that uses many custom, reusable elements.

When upgrading individual elements, you can choose to upgrade to either hybrid mode (if supporting both 1.x and 2.x simultaneously is important), or to upgrade directly to legacy mode or class-based mode. For simple elements, just updating the DOM template and styling may be enough to get the element running in 2.x legacy mode.

For a small application, converting your own elements to hybrid mode is probably not useful. The simplest incremental approach is to convert each element's DOM template first and get it running in legacy mode. Then you can port elements to class-based mode as desired.

For large applications, where you have written many of your own reusable elements, you may want to upgrade elements individually to hybrid mode.

Before you start the upgrade, there's a couple of things you need to do.

  • Update the Polymer CLI.
  • Create a new branch or workspace.
  • Update bower dependencies.

Update the Polymer CLI to the next version:

npm update polymer-cli@next

This should go without saying, but you'll want to work in a new branch or workarea so you don't break the existing, 1.x version of your element or app.

Update the Polymer version in bower.json to the latest RC version.

| Component | Version | | Polymer | 2.0.0-rc.1 | | webcomponentsjs | 1.0.0-rc.4 | | web-component-tester | 6.0.0-prerelease.5 | | Polymer elements | 2.0-preview |

Example dependencies

  "dependencies": {
    "app-layout": "PolymerElements/app-layout#2.0-preview",
    "app-route": "PolymerElements/app-route#2.0-preview",
    "iron-flex-layout": "PolymerElements/iron-flex-layout#2.0-preview",
    "iron-icon": "PolymerElements/iron-icon#2.0-preview",
    "iron-iconset-svg": "PolymerElements/iron-iconset-svg#2.0-preview",
    "iron-localstorage": "PolymerElements/iron-localstorage#2.0-preview",
    "iron-media-query": "PolymerElements/iron-media-query#2.0-preview",
    "iron-pages": "PolymerElements/iron-pages#2.0-preview",
    "iron-selector": "PolymerElements/iron-selector#2.0-preview",
    "paper-icon-button": "PolymerElements/paper-icon-button#2.0-preview",
    "polymer": "Polymer/polymer#2.0.0-rc.1",
    "webcomponentsjs": "webcomponents/webcomponentsjs#1.0.0-rc.4"
  },
  "devDependencies": {
    "web-component-tester": "6.0.0-prerelease.5"
  },

Run bower install to install the new dependencies.

If you are upgrading the element to hybrid mode, you can add extra sets of bower dependencies so you can test against multiple versions of Polymer easily. For details, see Manage dependencies for hybrid elements.

When upgrading an individual element, start by updating the DOM template and styling. For simple elements, this may be the only change you need to make to run in hybrid or legacy mode.

Polymer 2.0 elements create shadow DOM v1 shadow trees. As such, user code related to scoped styling, distribution, and events must be adapted to the native v1 API.

All elements need to update their shadow DOM templates and styling as described in this section.

Quick summary:

  • Remove deprecated patterns in the DOM module (<dom-module> using is or name; styles outside of the template).
  • Update your element's DOM template to use the new <slot> element instead of <content>.
  • Update styles to use the ::slotted() selector in place of ::content.
  • Remove any /deep/ and ::shadow CSS rules.
  • Update any URLs inside the template.

These changes are detailed in the following sections.

Your <dom-module> must set the id property to specify the element name. Polymer 1.x accepted the deprecated is or name as alternatives.

Make sure your element's styles are defined inside the template. Defining styles outside the template was deprecated in Polymer 1.x and is no longer supported.

Before

<dom-module name="blue-element">
  <template>
    <div>I am blue!</div>
  </template>
  <style>
    :host { color: blue; }
  </style>
</dom-module>

After

<dom-module id="blue-element">
  <template>
    <style>
      :host { color: blue; }
    </style>
    <div>I am blue!</div>
  </template>
</dom-module>
  • Change <content> insertion points to <slot> elements.

  • Change <content select="..."> to named slots: <slot name="...">.

Before

<!-- element template -->
<dom-module id="my-el">
  <template>
    ...
    <h2>
      <content select=".title"></content>
    </h2>
    <div>
      <content></content>
    </div>
  </template>
</dom-module>

  ...
<!-- usage -->
<my-el>
  <span class="title">Mr. Darcy</span>
  <span>Fun at parties.</span>
</my-el>

After

<!-- element template -->
<dom-module id="my-el">
  <template>
    ...
    <h2>
      <slot name="title"></slot>
    </h2>
    <div>
      <slot></slot>
    </div>
  </template>
</dom-module>

 ...
<!-- usage -->
<my-el>
  <span slot="title">Mr. Darcy</span>
  <span>Fun at parties.</span>
</my-el>

Note that if you're using <content select="..."> anywhere in your code, this means a change to your element's contract, and everyone using your element will need to update to use slot names.

Limitation. Slots can only be selected explicitly, by slot name. It's impossible to select content implicitly, based on a tag name or an arbitrary selector like :not(.header).

Can't be upgraded automatically

<!-- element template -->
<dom-module id="my-el">
  <template>
    ...
    <div class="header">
      <!-- Selection by tag name isn't supported.
           This element must be redesigned to work with
           Shadow DOM v1. -->
      <content select="h2"></content>
    </div>
    <div class="body">
      <content></content>
     </div>
    </template>
</dom-module>

<!-- usage -->
<my-el>
  <h2>Mr. Darcy</h2>
  <span>Not so fun at parties.</span>
</my-el>

Default slot versus default insertion point. In v0, a default insertion point (one without a select attribute) consumes all nodes not matched by a previous insertion point. In v1, a default slot (one without a name attribute) only matches content with no slot attribute. In other words, a node with a slot attribute is never distributed to the default slot.

<!-- shadow DOM v0 template -->
<template>
  <!-- this insertion point gets everything -->
  <content></content>
  <!-- the following insertion point never matches anything; the default
         Insertion point is greedy. -->
  <content select=".special"></content>
</template>

<!-- shadow DOM v1 template -->
<template>
  <!-- this slot gets any top-level nodes that don't have slot
       attributes. -->
  <slot></slot>
  <!-- the following insertion point gets any top-level nodes that have
       slot="special". -->
  <slot name="special"></slot>
  <!-- top-level nodes that have slot attributes with other values
       don't get distributed at all. -->
</template>

If you have complex distributions, and you're trying to upgrade to hybrid elements, you may need to place both <content> and <slot> elements in the template.

Multilevel distribution works differently in shadow DOM v1. In v0, content was redistributed at each level. For example, an element with class="title" can be distributed through several insertion points and eventually selected by a <content select=".title">.

Document content

<!-- v0 redistribution example -->
<parent-el>
  <span class="title">My Title</span>
</parent-el>

Shadow DOM of <parent-el>

<child-el>
  <content></content>
</child-el>

Shadow DOM of <child-el>

<h2>
  <!-- matches the span from the main document! -->
  <content select=".title"></content>
<h2>

By contrast, in v1, a host's light DOM nodes are only assigned to slots in the host's shadow DOM, based on the slot attributes on those light DOM nodes. For a parent element to redistribute content to a named slot, it must use a slot with a slot attribute.

<!-- v1 redistribution example -->
<parent-el>
  <span slot="header-text">My Title</span>
</parent-el>

Shadow DOM of <parent-el>

<child-el>
  <!-- matches span[slot=header-text] above, targets content for
       slot[name=title] below -->
  <slot slot="title" name="header-text"></slot>
</child-el>

Shadow DOM of <child-el>

<h2>
  <!-- matches the span from the main document! -->
  <slot name="title"></slot>
<h2>

Using the v0 approach does not work in v1:

<!-- v1 redistribution example -->
<parent-el>
  <span slot="title">My Title</span>
</parent-el>

Shadow DOM of <parent-el>

<child-el>
  <!-- default slot doesn't match span[slot=title] -->
  <slot></slot>
</child-el>

Shadow DOM of <child-el>

<h2>
  <!-- doesn't match the default slot from parent-el -->
  <slot name="title"></slot>
<h2>

In Polymer 1.x, URLs in attributes and styles inside element templates were re-written to be relative to the HTML import that defined the element. Based on user feedback, we are changing this behavior.

Two new properties are being added to Polymer.Element: importPath and rootPath. The importPath property is a static getter on the element class that defaults to the element HTML import document URL and is overridable. It may be useful to override importPath when an element template is not retrieved from a dom-module or the element is not defined using an HTML import. The rootPath property is set to the value of Polymer.rootPath which is globally settable and defaults to the main document URL. It may be useful to set Polymer.rootPath to provide a stable application mount path when using client side routing. URL's in styles are re-written to be relative to the importPath property. Inside element templates, URLs in element attributes are no longer re-written. Instead, they should be bound using importPath or rootPath where appropriate. For example:

A Polymer 1.x template that included:

<img src="foo.jpg">

In Polymer 2.x should be:

<img src$="[[importPath]]foo.jpg">

The importPath and rootPath properties are being ported back to Polymer 1.x, so they can be used by hybrid elements.

Update styles in your shadow DOM to match v1 specifications:

  • Replace ::content selectors with ::slotted() selectors.
  • Remove /deep/ and ::shadow selectors, if you're still using them.
  • Remove :root selectors.
  • Update custom property syntax.
  • Wrap custom-style elements.

Replace any ::content CSS selectors with ::slotted(selector) where selector is compound selector that identifies a top-level distributed child.

That is:

::slotted(.foo)

is equivalent to:

::content > .foo

Example of ::slotted

<dom-module id="slotted-el">
  <template>
    <style>
      #container ::slotted(*) {
        color: #blue;
      }
      #container ::slotted(.warning) {
        color: red;
      }
    </style>
    <div id="container">
      <slot></slot>
    </div>
  </template>
</dom-module>

<!-- usage -->
<slotted-el>
  <div>
    I'm colored blue.
  </div>
  <div class="warning">
    I'm colored red.
  </div>
  <div>
    <span class="warning">Surprise! Not red.</span>
  </div>
</slotted-el>

In shadow DOM v1, you cannot select a descendant of a top-level distributed child. For example, you can't select the span in the previous example like this:

#container ::slotted(*) span { ... }

No selectors can follow the ::slotted() selector.

For more information, see Styling distributed nodes in the Web Fundamentals article on shadow DOM.

If you still have any /deep/ or ::shadow selectors in your project, it's time to remove them. They don't work at all in shadow DOM v1.

There's no direct substitute for shadow-piercing selectors.To let users customize your element, custom CSS properties are probably the best option.

If you're using the :root selector inside an element's template, replace it with:

:host > * { ... }

(If you're using :root selection inside a custom-style, replace it with the html selector.)

In 1.x, you can use either of these selectors to override custom properties set at a higher level. For example, the following rule in the main document:

style-me {
  --my-theme-color: blue;
}

Overrides a :host rule in style-me's shadow root, because they match the same element, but the main document styles comes earlier in the cascade order. :host > * applies a ruleset to all of the top-level children in the host's shadow tree, which doesn't conflict with the rule in the main document.

Before

<style>
  :root {
    --my-theme-color: red;
  }
</style>

After

<style>
  :host > * {
    --my-theme-color: red;
  }
</style>

When applying custom properties, Polymer 1.x accepted this incorrect syntax for specifying a default value to a var() function:

Before

color: var(--special-color,--default-color);

By specification, the default (or fallback) is a CSS value, not a custom property name. To use a custom property as a default, add a nested var() function.

After

color: var(--special-color, var(--default-color));

In addition, you must update the syntax of any @apply rules to match the proposal, which doesn't use parentheses.

Before

@apply(--my-mixin);

After

@apply --my-mixin;

While custom elements v1 supports customized built-in elements, Polymer 2.0 does not currently use them. Instead, it introduces a new <custom-style> element that wraps a <style> element.

  • Hybrid projects. Wrap your existing <style is="custom-style"> elements with <custom-style> elements.
  • 2.0-only projects. Replace your existing <style is="custom-style"> elements with <custom-style> elements.
  • All projects. Replace any :root selectors with html, and update custom property syntax as described in Update custom property syntax.

Before

<style is="custom-style">
  /* In a 1.x custom-style, :root can be used to set global defaults */
  :root {
    --my-theme-color: #9C27B0;
  }
</style>

After (hybrid code):

<custom-style>
  <style is="custom-style">
    /* In a 2.x custom-style use the html selector to set global defaults */
    html {
      --my-theme-color: #9C27B0;
    }
  </style>
</custom-style>

After (2.x-only code):

<custom-style>
  <style>
    html {
      --my-theme-color: #9C27B0;
    }
  </style>
</custom-style>

Hybrid and legacy elements can continue to use existing Polymer DOM APIs, but may require some changes. Class-based elements should use native DOM APIs.

If your element doesn't do any imperative DOM manipulation, you can skip this section.

  • Hybrid elements should continue to use the Polymer DOM APIs, but may require some changes.
  • Legacy elements can use the Polymer DOM APIs or the native DOM APIs.
  • Class-based elements should use native DOM APIs.

Hybrid elements need to keep using the Polymer.dom API. However, note that in 2.0, for native methods and properties that return a NodeList, **Polymer.dom APIs also return NodeList, not Array. If you're using any native Array methods on the returned object, you need to update your code.

Legacy elements may continue using Polymer DOM APIs or move to native methods as described in Use native DOM methods

Before

_findTextNodes: function() {
  // childNodes is an Array
  var nodes = Polymer.dom(this).childNodes;
  return nodes.filter(function(n) { return n.nodeType = Node.TEXT_NODE });
}

After

_findTextNodes: function() {
  // childNodes *might* be a NodeList
  var nodes = Polymer.dom(this).childNodes;
  return Array.prototype.filter.call(nodes,
      function(n) { return n.nodeType = Node.TEXT_NODE });
}

If you are targeting 2.0 only, use the native DOM APIs instead of the Polymer.dom APIs.

  • For standard DOM operations, simply remove the Polymer.dom() wrapper.
  • Use this.shadowRoot in place of Polymer.dom(this.root).
  • For events, use the standard v1 event API:
    • Polymer.dom(event).localTarget becomes event.target.
    • Polymer.dom(event).path becomes event.composedPath().
    • Polymer.dom(event).rootTarget becomes event.composedPath()[0].

On browsers that lack native shadow DOM v1 support, Polymer 2.0 is designed to be used with the new shady DOM v1 polyfill, which patches native DOM API as necessary to be mostly equivalent to native shadow DOM.

Note that the new ES6 base class, Polymer.Element, lacks many of the DOM helpers, such as getContentChildren and getEffectiveChildren, defined on Polymer.Base. Polymer encourages the use of native properties and methods where possible. See Slot APIs for information on some of the new shadow DOM-related APIs.

The Polymer.FlattenedNodesObserver class can be used to replace the 1.x observeNodes method.

1.x

this._observer = Polymer.dom(this).observeNodes(this._nodesChanged);

2.x

this._observer = new Polymer.FlattenedNodesObserver(this._nodesChanged);

In addition, Polymer.FlattenedNodesObserver.getFlattenedNodes(node) can be used to replace the getEffectiveChildNodes method.

Polymer.FlattenedNodesObserver is an optional module. If you're loading the polymer-element.html import, you need to import FlattenedNodesObserver separately.

<link rel="import" href="/bower_components/polymer/lib/utils/flattened-nodes-observer.html">

Polymer 2.0 continues to use a shim to provide limited support for CSS custom properties on browsers that do not yet natively support custom properties. This lets an element expose a custom styling API. The shim is now included as part of the shady CSS polyfill, not in the Polymer library itself.

Support for custom CSS mixins has been moved to a second, optional shim.

The following changes have been made in the shims that Polymer 2.0 uses:

  • The shim always uses native CSS custom properties on browsers that support them. This was optional in 1.x, and it introduces some limitations on the use of mixins.
  • CSS mixin support has been separated into an optional shim.
  • The customStyle instance property has been removed. Use updateStyles instead.
  • Invalid custom properties syntax is no longer supported. These changes are described in Shadow DOM styles.

CSS custom properties are becoming widely supported, CSS mixins remain a proposal. So support for CSS mixins has been moved to a separate shim that is optional for 2.0 class-style elements. For backwards compatibility, the polymer.html import includes the CSS mixin shim. Class-style elements must explicitly import the mixin shim.

The shim performs a one-time transformation of stylesheets containing CSS custom property mixins. Where possible, mixins are transformed into individual native CSS properties for better performance.

This introduces some limitations to be aware of, which are documented in the shady CSS polyfill README.

The following examples show how to load the CSS mixin shim.

Example: importing CSS mixin shim to top-level file

<!-- load polyfills -->
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
<!-- import CSS mixin shim -->
<link rel="import" href="/bower_components/shadycss/apply-shim.html">
<!-- import custom-style -->
<link rel="import" href="/bower_components/polymer/lib/elements/custom-style.html">

<custom-style>
  <style>
    html {
      --my-mixin: {
        color: blue;
      }
    }
  </style>
</custom-style>

Example: importing CSS mixin shim to an element

<!-- import CSS mixin shim -->
<link rel="import" href="../shadycss/apply-shim.html">
<!-- import Polymer.Element -->
<link rel="import" href="../polymer/polymer-element.html">

<dom-module is="x-custom">
  <template>
    <style>
      :host {
        @apply --my-mixin;
      }
    </style>
  </template>
  <script>
    class XCustom extends Polymer.Element {
      static get is() {return 'x-custom'}
    }

    customElements.define(XCustom.is, XCustom);
  </script>
</dom-module>

Instead of using the customStyle object, pass new style properties to the updateStyles method. This use of updateStyles was already supported in 1.x. The customStyle object is removed in 2.0.

Before

this.customStyle['--my-dynamic-property'] = 'red';
this.updateStyles();

After

this.updateStyles({'--my-dynamic-property': 'red'});

Polymer 2.0 elements target the custom elements v1 API.

  • All elements. The contracts have changed for several of the lifecycle callbacks. Check and test your code to make sure this doesn't cause problems with your elements.
  • All elements. Refactor type-extension elements as wrapper elements. Wrap existing type-extension elements.

Polymer 2.0 introduces several changes to the contracts of the various lifecycle callbacks. Some of these are required by the custom elements v1 specification, while others are designed to improve performance.

For more information on the lifecycle callbacks, see Lifecycle changes.

The custom elements v1 specification forbids reading attributes, and accessing child or parent information from the DOM API from the constructor. Likewise, attributes and children cannot be added. You need to move any DOM work out the constructor:

  • Defer work until after the constructor completes using setTimeout or requestAnimationFrame.
  • Move work to a different callback,such as attached/connectedCallback or ready.
  • Use an observer, slotchange event listener, or mutation observer to react to runtime changes.

The ready callback, for one-time initialization, signals the creation of the element's shadow DOM. In the case of class-based elements, you need to call super.ready() before accessing the shadow tree.

The major difference between 1.x and 2.0 has to do with the timing of initial light DOM distribution. In the v1 shady DOM polyfill, initial distribution of children into <slot> is asynchronous (microtask) to creating the shadowRoot, meaning distribution occurs after observers are run and ready is called. In the Polymer 1.0 shim, initial distribution occurred before ready.

To check the initial distribution, use setTimeout or requestAnimationFrame from ready. The callback should fire after initial distribution is complete.

Class-based element: check distributed nodes

ready() {
  super.ready();
  setTimeout(function() {
    var distributedNodes = this.$.slot.assignedNodes({flatten: true});
    console.log(distributedNodes);
  }.bind(this), 0);
}

You can use a slotchange event listener to react to runtime changes to distribution, but the event listener doesn't fire for the initial distribution.

Class-based element: slotchange listener

ready() {
  super.ready(); // for 2.0 class-based elements only
  this._boundHandler = this._processLightChildren.bind(this);
  setTimeout(this._boundHandler);
  this.$.slot.addEventListener('slotchange', this._boundHandler);
}

_processLightChildren: function() {
  console.log(this.$.slot.assignedNodes());
}

In order to force distribution synchronously, call ShadyDOM.flush(). This can be useful for unit tests.

The hybrid or legacy equivalents of the above samples would use the 1.x APIs, like getContentChildNodes or observeNodes.

Hybrid element: get distributed nodes

ready: function() {
 setTimeout(function() {
   var distributedNodes = this.getContentChildNodes();
   console.log(distributedNodes);
 }.bind(this), 0);
}

You can use observeNodes method to react to runtime changes to distribution.

Hybrid element: observeNodes

ready: function() {
  this._observer = Polymer.dom(this.$.contentNode).observeNodes(function(info) {
      this.processNewNodes(info.addedNodes);
      this.processRemovedNodes(info.removedNodes);
  });
}

For more details on observeNodes, see Observe added and removed children in the Polymer 1 documentation.

In order to force distribution synchronously, call Polymer.dom.flush. This can be useful for unit tests.

In 2.x, Polymer.dom.flush does not flush the observeNodes callbacks. To force the observeNodes callbacks to be invoked, call the flush method on the observer object returned from observeNodes.

If you have any code that relies on the element being layed out when the attached callback runs (for example, measuring the element or its children), it must wait until the layout is complete.

Use the Polymer.RenderStatus.beforeNextRender function to register a one-time callback after layout is complete, but before the page is rendered (or "painted").

Before

attached: function() {
  // measure something
}

After

attached: function() {
  // 1st argument to beforeNextRender is used as the "this"
  // value when the callback is invoked.
  Polymer.RenderStatus.beforeNextRender(this, function() {
     // measure something
  });
}

For work that can be deferred until after first paint (such as adding event listeners), you can use Polymer.RenderStatus.afterNextRender, which takes the same arguments as beforeNextRender.

These examples show the hybrid callbacks, but the Polymer.RenderStatus API can be used in class-based elements as well. If you're loading the polymer-element.html import, you need to import FlattenedNodesObserver separately.

<link rel="import" href="/bower_components/polymer/lib/utils/flattened-nodes-observer.html">

Polymer doesn't support type-extension elements (such as <input is="iron-input">). For a discussion of this change, see Type-extension elements

  • All projects. Refactor your own type-extension elements.
  • All projects. Replace any top-level template extension elements with the 2.0 wrapper equivalents.

Type extension elements need to be refactored into standard custom elements (for example, instead of an element that extends an <a> element, an element that takes an <a> element in its light DOM).

If you have any template extension elements—dom-bind, dom-if, or dom-repeat—in the main document, convert them to the wrapped form.

Before

<template is="dom-bind">
  <ul>
  <template is="dom-repeat" items="{{people}}">
    <li>{{item.name}}
  </template>
  </ul>
</template>
<script>
var domBind = document.querySelector('template[is=dom-bind]');
domBind.people = [
 ...
];
</script>

After

<dom-bind>
  <!-- Hybrid code must keep the is="dom-bind" for backwards
       compatibility. For 2.0-only projects, use a plain template. -->
  <template is="dom-bind">
    <ul>
    <!-- inner template doesn't need to be wrapped -->
    <template is="dom-repeat" items="{{people}}">
      <li>{{item.name}}
    </template>
    </ul>
  </template>
</dom-bind>
<script>
var domBind = document.querySelector('dom-bind');
domBind.people = [
 ...
];
</script>

Polymer automatically wraps template extensions used in Polymer element templates during template processing. This means you can and should continue using <template is=""> in templates nested inside a Polymer element template. As shown above, nested templates inside a top-level dom-bind, dom-if, or dom-repeat are also automatically wrapped.

Templates used in the main document must be manually wrapped.

Polymer 2.0 includes several important changes to the data system, detailed in Data system improvements.

Code that interacts with key paths, or uses Polymer.Collection directly won't run in hybrid mode. If upgrading to hybrid mode, you can conditionalize 1.0 code:

if (Polymer.Element) {
  // 2.0 code
} else {
  // 1.0 code
}

If upgrading to legacy or class-based elements, you can eliminate this code. Array change notifications for specific items use index paths. Changing the entire array results in a change notification for the entire array.

Observers need to check for undefined arguments, which was not an issue in 1.x. If all of your observer's dependencies have default values, the observer should not be called with undefined arguments. But if your code relies on the observer to wait until all dependencies are defined, you need to add undefined checks.

If your observer relies on hidden dependencies being initialized, you may need to update your code.

Before

observers: [ '_observeStuff(a, b)' ],
_observeStuff: function(a, b) {
  // this.c == hidden dependency!
  var v = a * b + this.c;
  ...
}

After

observers: [ '_observeStuff(a, b, c)' ],
_observeStuff: function(a, b, c) {
  // check for undefined
  if (! (a === undefined || b === undefined || c === undefined)) {
    // no hidden dependency
    var v = a * b + c;
    ...
  } else {
    // handle undefined case.
  }
}

A few more changes that you may need to take into account.

Only properties listed explicitly in properties can be configured from an attribute. You need to explicitly declare your property if both of the following are true:

  • You have a property that's declared implicitly (used in a binding or observer, but not in the properties object).
  • You rely on configuring that property from an attribute (not a data binding).

Because several aspects of timing change in 2.0, you'll need to test your code to ensure that it doesn't rely on any 1.x timing. In particular:

  • Element initialization (including template stamping and data system initialization) is deferred until the the element is connected to the main document. (This is a result of the custom element v1 changes.)

In order for a property to be deserialized from its attribute, it must be declared in the properties metadata object

Binding a default value of false using an attribute binding to a boolean property will not override a default true property of the target, due to the semantics of boolean attributes. In general, property binding should always be used when possible, and will avoid such situations.

The following APIs have been removed.

  • Polymer.instanceof and Polymer.isInstance: no longer needed, use instanceof and instanceof Polymer.Element instead.
  • element.getPropertyInfo: This API returned unexpected information some of the time and was rarely used.

  • element.getNativePrototype: Removed because it is no longer needed for internal code and was unused by users.

  • element.getNativePrototype: Removed because it is no longer needed for internal code and was unused by users.

  • element.beforeRegister: This was originally added for metadata compatibility with ES6 classes. We now prefer users create ES6 classes by extending Polymer.Element, specifying metadata in the static properties and observers properties.

    For legacy elements, dynamic effects may now be added using the registered lifecycle method.

  • element.attributeFollows: Removed due to disuse.

  • element.classFollows: Removed due to disuse.

  • listeners Removed ability to use id.event to add listeners to elements in shadow DOM. Use declarative template event handlers instead.

  • Methods starting with _ are not guaranteed to exist (most have been removed).

To define a class-based element, create a class that extends Polymer.Element (a subclass of HTMLElement), which provides most of the same features of Polymer 1.x based on static configuration data supplied on the class definition.

The basic syntax looks like this:

<!-- Load the Polymer.Element base class -->
<link rel="import" href="bower_components/polymer/polymer-element.html">

<script>
// Extend Polymer.Element base class
class MyElement extends Polymer.Element {

  static get is() { return 'my-element'; }

  static get properties() {
    return {
      /* properties meta data object just like 1.x */
      myProp: {
        type: Object,
        notifies: true
      }
    }
  }

  static get observers() {
    return [
      /* observer array just like 1.x */
      '_myPropChanged(myProp.*)'
    ]
  }

  constructor() {
    super();
    ...
  }

  connectedCallback() {
    super.connectedCallback();
    ...
  }

  myPropChanged(changeRecord) {
    ...
  }
  ...
}

// Register custom element definition using standard platform API
customElements.define(MyElement.is, MyElement);
</script>

Below are the general steps for defining a custom element using this new syntax:

  • Extend from Polymer.Element. This class provides the minimal surface area to integrate with 2.0 DOM templating and data binding system. It provides the standard custom element lifecycle callbacks, plus the Polymer-specific ready callback.

  • Implement "behaviors" as mixins that return class expressions. Or use the the mixinBehaviors method to mix hybrid behaviors into your element.

  • Element's is property should be defined as a static on the class.

  • The listeners and hostAttributes have been removed from element metadata; listeners and default attributes can be installed as and when needed. For convenience _ensureAttribute is available to set default attributes.

    // set tabindex if it's not already set
    _ensureAttribute('tabindex', 0);
    

Note that Polymer.Element provides a cleaner base class without much of the sugared utility API that present on legacy elements, such as fire, transform, and so on. With web platform surface area becoming far more stable across browsers, we intend to add fewer utility methods and embrace the raw platform API more. So when using Polymer.Element, instead of using the legacy this.fire('some-event') API, use the equivalent platform APIs:

this.dispatchEvent(new CustomEvent('some-event', { bubbles: true }));

(The CustomEvent constructor is not supported on IE, but the webcomponents polyfills include a small polyfill for it so you can use the same syntax everywhere.)

In addition, many features are still included in the library, but as optional modules or mixins rather than being bundled in with Polymer.Element. For details, see Import optional features

If you want to upgrade to a class-based element but depend on some of the removed APIs, you can add most of the legacy APIs by using the LegacyElementMixin.

class MyLegacyElement extends Polymer.LegacyElementMixin(Polymer.Element) { ... }

A class expression mixin is essentially a factory function that takes a class as an argument and returns a new class, with new features "mixed in." Polymer 2.x provides a number of features as optional mixins instead of building them into the base class.

Apply mixins when you create an element class:

class MyElement extends MyMixin(Polymer.Element) {
  static get is() { return 'my-element' }
}

The MyMixin(Polymer.Element) returns a new class, which extends Polymer.Element and adds the features from MyMixin. So MyElement's inheritance is:

MyElement => MyMixin(Polymer.Element) => Polymer.Element

For information on writing your own class expression mixins, see Sharing code with class expression mixins

In some cases, the features you want to use may be available as hybrid behaviors, but not as class mixins.

You can add hybrid behaviors to your class-style element using the Polymer.mixinBehavior function:

class XClass extends Polymer.mixinBehaviors([MyBehavior, MyBehavior2], Polymer.Element) {
  static get is() { return 'x-class'}

  ...
}
customElements.define(XClass.is, XClass);

The mixinBehavior function also mixes in the Legacy APIs, the same as if you extended Polymer.LegacyElement. These APIs are required since since hybrid behaviors depend on them.

A number of features have been omitted from the base Polymer.Element class and packaged as separate, optional imports. These include:

Element imports are found in the Polymer folder under /lib/elements, mixins under /lib/mixins, and utility modules under /lib/utils. For example, to load the Polymer.RenderStatus module, use an import like this:

<link rel="import" href="/bower_components/polymer/lib/utils/render-status.html">