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.
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);
4 Likes
Really nice detailed reply and blog post. Good point about the way you need to define getters and setters.
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.
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/
1 Like