this.querySelector(".elem").addEventListener("click", this.onClick.bind(this))

// とか

const tpl = html`<button on-click="${this.onClick.bind(this)}"></button>`

毎回 bind 書くのってすごく面倒
なので自動でするようにしました

class ExampleElement extends HTMLElement {
constructor() {
super()
for (const [key, fn] of Object.entries(this.listeners)) {
this[key] = fn.bind(this)
}
}

get listeners() {
return {
onClick() { console.log(this) }
}
}

example() {
this.querySelector("button").addEventListener("click", this.onClick)
}
}

listeners の getter でリスナ関数を返すようにする
コンストラクタで listeners で取得できるの全てに this を bind してプロパティに設定する
example メソッドのように 使うとき bind が不要になる
コンストラクタの処理は共通のベースクラスにでも書いておくといい感じ

コンストラクタで何もしない版

class ExampleElement extends HTMLElement {
get listeners() {
return {
onClick: () => { console.log(this) }
}
}

example() {
this.querySelector("button").addEventListener("click", this.listeners.onClick)
}
}

listeners を挟むけどわかりやすいといえばわかりやすい
アロー関数にしたらなにもしなくても this がクラスのインスンタンスに固定される

this.listeners = this.listeners

しておくほうがリスナ関数が同じ値になっていいかも