Webcomponents in Aurelia


#1

Hi,

We saw here that Aurelia is able to play with webcomponents. I knew it for react or polymer components, but I’m now interested about standard customElements. In this thread, we can see a gist working with a specially bundled webcomponent. But I’m not able to create my own by following standard (ES6, link import, etc):

  • If I use import, the component will mount but it will ignore the attr.bind attribute.
  • If I import the script in my ViewModel, I have the following error: “Uncaught TypeError: Failed to construct ‘HTMLElement’: Please use the ‘new’ operator, this DOM object constructor cannot be called as a function” but the gist show us that it’s possible.

Here would be a good place to centralize all informations about integrating a standard webcomponent in Aurelia.


#2

First of all if you’re using the jspm skeleton you can follow my blog post Webcomponents in Aurelia

Your first point about the attribute binding might come from Aurelia not rendering the attribute value but using the property. So you have to add a getter and setter for the attributes in your web component.


class MyComponent extends HTMLElement {
  createdCallback() {
    // render the component and do some stuff
  }
  get attrOne() {
    return this._attrOne;
  }
  set attrOne(value) {
    this._attrOne = value;
    // Re-render related parts
  }
}

For the second part you might forgot to register your web component with the custom elements api

CustomElements v1

customElements.define('my-component', MyComponent);

older CustomElements v0

document.registerElement('my-component', MyComponent);

#3

Really nice detailed reply and blog post. Good point about the way you need to define getters and setters.


#4

Here is what I tried to do with a aurelia-cli (requirejs) app:

components/test-custom.html

<script>
class TestCustom extends HTMLElement {
  static get observedAttributes() {return ['message']; }

  get message() {
    return this._message;
  }

  set message(message) {
    this._message = message;
    this.render();
  }

  constructor() {
    super();
    this.shadowDOM = this.attachShadow({mode: 'open'});
  }

  connectedCallback() {
    this.render();
  }

  attributeChangedCallback(attributeName, oldValue, newValue, namespace) {
    console.log(attributeName, newValue);
    this[attributeName] = newValue;
    this.render();
  }

  render() {
    this.shadowDOM.innerHTML = `
    <div style="background: red">
      <p>Hello ${this._message || ''}</p>
      <p><slot></slot></p>
    </div>
    `;
  }
}
customElements.define('test-custom', TestCustom);
</script>

app.html

<template>
<link
  rel="import"
  href="components/test-custom.html">
<input
  value.bind="message">
<test-custom
  message.bind="message">Pwet</test-custom>
</template>

The component is rendered, but the message attribute is never changed (attributeChangedCallback method is not called when changing message value from the input.


#5

I have writing a CustomElement which make working a standard customElement:

resources/elements/custom-element-proxy.js

import {bindable, inlineView, inject} from 'aurelia-framework';

@inlineView('<template><slot></slot></template>')
@inject(Element)
export class CustomElementProxyCustomElement {
  @bindable customElement;
  @bindable attributes;
  element;
  customEl;

  constructor(element) {
    this.element = element;
  }

  attached() {
    this.customEl = document.createElement(this.customElement);
    const children = this.element.childNodes;
    while(children.length) {
      this.customEl.appendChild(children[0]);
    }
    this.element.appendChild(this.customEl);
    this.attributesChanged(this.attributes);
  }

  attributesChanged(newVal) {
    if (!this.customEl) return;
    for (const attr of Object.keys(newVal)) {
      this.customEl.setAttribute(attr, newVal[attr]);
    }
  }
}

app.html

<template>
<link
  rel="import"
  href="components/test-custom.html">
<require from="resources/elements/custom-element-proxy"></require>
<input
  value.bind="message">
<input
  value.bind="content">
<custom-element-proxy
  custom-element="test-custom"
  attributes.bind="{ message: message }">Content: ${content}</custom-element-proxy>
</template>

And the test-custom component is the same than here.

And it works \o/