Mixins

Inheritance in Polymer is implemented with so called mixins. A mixin is a set of methods, properties, observers and lifecycle callback methods that can be inherited by any Polymer component.

Each web component can use any number of mixins. Web components can use mixins' methods and properties as if they were their own. And mixins can use web components' methods and properties.

Below is an example demonstrating how to write and use mixins. It’s a spelling improvement program that offers a user to type a word. If the user fails to spell it correctly then the input will be highlighted with red. On any typing the highlighting will be removed. Logic for setting/removing error state in the component is implemented by a mixin called ValidatedElementMixin.

Spelling checker

Source code

index.html
<html>
<head>
	<link rel="import" href="src/polymer-advanced/mixins/spelling-checker.html">
	<script src="bower_components/webcomponentsjs/webcomponents-loader.js"></script>
</head>
<body>
    <spelling-checker word="Elephant"></spelling-checker>
</body>
</html>
src/polymer-advanced/mixins/spelling-checker.html
<link rel="import" href="../../../bower_components/polymer/polymer-element.html">
<link rel="import" href="input-with-validation.html">

<dom-module id="spelling-checker">
  <template>

    <input-with-validation id="wordInput" value="{{typedValue}}">
      Please try to enter word "[[word]]" without mistakes
    </input-with-validation>
    <button on-click="checkWord">Check</button>

  </template>
  <script>
    class SpellingChecker extends Polymer.Element {
      static get is() {
        return 'spelling-checker';
      }

      static get properties() {
        return {
          typedValue: String,
          word: String
        }
      }

      checkWord() {
        if (this.typedValue === this.word) {
          alert('Great! You did it!');
        } else {
          this.$.wordInput.setError('The word was not typed correctly. Please, try again!');
        }
      }
    }

    customElements.define(SpellingChecker.is, SpellingChecker);
  </script>
</dom-module>
src/polymer-advanced/mixins/input-with-validation.html
<link rel="import" href="../../../bower_components/polymer/polymer-element.html">
<link rel="import" href="../../../bower_components/polymer/lib/elements/dom-if.html">
<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
<link rel="import" href="validated-element-mixin.html">

<dom-module id="input-with-validation">
  <template>
    <style>
      .error-input-msg,
      input[error] {
        color: red;
      }
    </style>

    <label>
      <slot></slot>
    </label>

    <br/>
    <br/>

    <iron-input bind-value="{{value}}">
      <input error$="[[error]]" placeholder="[[placeholder]]" />
    </iron-input>

    <br/>
    <br/>

    <template is="dom-if" if="[[errorMsg]]">
      <div class="error-input-msg">[[errorMsg]]</div>
      <br/>
    </template>

  </template>
  <script>
    // Please note how we are inheriting our class from a mixin
    class InputWithValidation extends ValidatedElementMixin(Polymer.Element) {
      static get is() {
        return 'input-with-validation';
      }

      static get properties() {
        return {
          value: {
            type: String,
            // This attribute is required to enable 2-way binding.
            // If we don't set it then the client of the input-with-validation.html won't get notifications
            //  that the property has changed.
            notify: true
          }
        }
      }
    }

    customElements.define(InputWithValidation.is, InputWithValidation);
  </script>
</dom-module>
src/polymer-advanced/mixins/validated-element-mixin.html
<script>

  // Mixin can contain properties, observers, methods, lifecycle callback methods as a normal Polymer component
  ValidatedElementMixin = function(superClass) {
    // Actually a mixin function just returns a class extending a class passed to it
    return class extends superClass {
      static get properties() {
        return {
          error: Boolean,
          errorMsg: String
        };
      }

      static get observers() {
        return [
          '_resetError(value)'
        ];
      }

      setError(msgCode) {
        this.set('error', true);
        this.set('errorMsg', msgCode);
      }

      _resetError() {
        this.set('error', false);
        this.set('errorMsg', null);
      }
    }
  };

</script>

input-with-validation represents a common UI component that supports validation. ValidatedElementMixin can be used with other types of elements: comboboxes, text areas, radio-buttons, etc.

Tip

Please note what we marked the value property in input-with-validation.html with the notify attribute. It’s a necessary detail if want to allow clients of this element to use this property with 2-way binding.

In this example, we used just one mixin, but it’s possible to use any number of mixins by including them one into another. For example, we could create something like that:

class PowerfulInput extends ElementWithDebounceMixin(SelfPersistedElementMixin(ValidatedElementMixin(Polymer.Element)))
Tip

Please note that prior to version 2.0, Polymer used so called behaviors instead of mixins. They are elements similar to mixins with the same purpose and possibilities but using another syntax for creation and usage. You don’t need to create or use behaviors in your code but you might encounter them in third-party components. See more details in https://www.polymer-project.org/1.0/docs/devguide/behaviors.

What we have learned so far
  • We can use mixins to implement some common logic and share it between components.

  • Mixins can contain methods, properties, lifecycle callback methods and observers.

  • After extending a mixin, a Polymer component doesn’t know which properties/methods are inherited and which are not. So, the component treats them the same.

  • In order to allow some property to be used by clients in two-way binding, we have to mark it with the notify parameter.