Passing dow state means that we intertwine the “state data” tree and the “component tree”.
It will be way simpler to have one “tree state” and components subscribing to nodes of this tree !
javascriptimport { BehaviorSubject, tap } from "rxjs"; export class Component extends HTMLElement { shadow = this.attachShadow({ mode: "open" }); isRootState = false; _state$ = new BehaviorSubject(null); _observedAttributes$ = new BehaviorSubject(null); connectedCallback() { this._observedAttributes$.next( this.constructor.observedAttributes ?.map(attr => ({ [attr]: this.getAttribute(attr) })) .reduce((prev, curr) => ({ ...prev, ...curr }), {}) ); this._renderSubscription = this._state$ .pipe(tap(s => this.render(s))) .subscribe(); } disconnectedCallback() { this._stateSubscription?.unsubscribe(); this._renderSubscription?.unsubscribe(); } attributeChangedCallback(name, oldValue, newValue) { const newObservedAttributes = { ...this._observedAttributes$.value, ...{ [name]: newValue } }; this._observedAttributes$.next(newObservedAttributes); } templateStyle(state) { return ""; } template(state) { return ""; } render(state) { this.shadow.innerHTML = ` ${this.templateStyle(state)} ${this.template(state)} `; } rootState$() { const parentOrHost = el => el.parentNode ?? el.host; let parent = parentOrHost(this); while (parent) { if (parent.isRootState) { return parent.state$; } parent = parentOrHost(parent); } } set state$(state$) { this._stateSubscription?.unsubscribe(); this._stateSubscription = state$.subscribe(this._state$); } get state$() { return this._state$; } get observedAttributes$() { return this._observedAttributes$; } }