I have created a very basic custom element, that can change its value based on a provided attribute person
. But whenever I'm loading my custom element I get this error: Cannot set property 'innerHTML' of null
. When I add a breakpoint to the attributeChangedCallback function I can indeed see that on load the element is not there. When I continue loading though the element loads perfectly.
I could imagine because I'm using webpack to bundle all my files that the issue comes from loading the element at the end of the body instead of loading the element inside my head.
my-element.js:
class MyElement extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({mode: 'open'});
this._person = '';
}
get person() {
return this._name;
}
set person(val) {
this.setAttribute('person', val);
}
static get observedAttributes() {
return ['person'];
}
attributeChangedCallback(attrName, oldVal, newVal) {
let myElementInner = this.shadow.querySelector('.my-element-inner');
switch (attrName) {
case 'person':
this._person = newVal;
// ======================
// The error occures here
// ======================
myElementInner.innerHTML = `My name is ${this._person}`;
}
}
connectedCallback() {
var template =
`
<style>
.my-element-inner {
outline: blue dashed 1px;
background-color: rgba(0,0,255,.1);
}
</style>
<span class="my-element-inner">My name is ${this._person}</span>
`
this.shadow.innerHTML = template;
}
}
customElements.define('my-element', MyElement);
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebPack Test Page</title>
</head>
<body>
<my-element person="André"></my-element>
<!-- Here goes the bundle.js -->
</body>
</html>
The attributeChangedCallback()
can be called before or after the connectedCallback
depending on how your custom element is used.
If you move the connectedCallback
logic to the constructor then things will be fine
Another option would be to check if myElementInner
is null
and keep your code in the connectedCallback
class MyElement extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({mode: 'open'});
this._person = '';
var template =
`
<style>
.my-element-inner {
outline: blue dashed 1px;
background-color: rgba(0,0,255,.1);
}
</style>
<span class="my-element-inner">My name is ${this._person}</span>
`
this.shadow.innerHTML = template;
}
get person() {
return this._person;
}
set person(val) {
this.setAttribute('person', val);
}
static get observedAttributes() {
return ['person'];
}
attributeChangedCallback(attrName, oldVal, newVal) {
let myElementInner = this.shadow.querySelector('.my-element-inner');
switch (attrName) {
case 'person':
this._person = newVal;
if (myElementInner) {
myElementInner.innerHTML = `My name is ${this._person}`;
}
}
}
}
customElements.define('my-element', MyElement);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebPack Test Page</title>
</head>
<body>
<my-element person="André"></my-element>
<!-- Here goes the bundle.js -->
</body>
</html>