The goal is to display a spreadsheet with 3 cells where A = 15, B = A + 1, C = A = B
html<!DOCTYPE html> <link rel="shortcut icon" href="#" /> <html lang="fr"> <body> <potions-cell reference="A"></potions-cell> <potions-cell reference="B"></potions-cell> <potions-cell reference="C"></potions-cell> <script src="main.js"></script> </body> </html>
The BehaviorSubject that is used to set the state of a component needs to be defined prior to defining the component.
javascriptimport { PotionsCell } from "./PotionsCell.js"; import { BehaviorSubject, map, combineLatest, delay } from "rxjs"; //window.spreadsheet$ = new BehaviorSubject(null); window.cells = { A: new BehaviorSubject(null), B: new BehaviorSubject(null), C: new BehaviorSubject(null) }; customElements.define("potions-cell", PotionsCell); window.cells.A.next(15); window.cells.A.pipe( map(v => v + 1), delay(5000) ).subscribe(window.cells.B); combineLatest(window.cells.A, window.cells.B) .pipe(map(([a, b]) => a + b)) .subscribe(window.cells.C);
The state is defined in the connectedCallback method.
We see here how the PotionsCell is made reactive to new observedAttributes.
javascriptimport { Component } from "./Component.js"; import { switchMap } from "rxjs"; export class PotionsCell extends Component { static get observedAttributes() { return ["reference"]; } connectedCallback() { super.connectedCallback(); this.state$ = super.observedAttributes$.pipe( switchMap(observedAttributes => { return window.cells?.[observedAttributes["reference"]]; }) ); } templateStyle(state) { return ` <style> :host { display:flex; border: 1px solid black; padding:20px; margin:10px; text-align:center; } </style>`; } template(state) { return state; } }
Oldjavascriptimport { BehaviorSubject, tap } from "rxjs"; export class Component extends HTMLElement { shadow = this.attachShadow({ mode: "open" }); _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)} `; } set state$(state$) { this._stateSubscription?.unsubscribe(); this._stateSubscription = state$.subscribe(this._state$); } get state$() { return this._state$; } get observedAttributes$() { return this._observedAttributes$; } }