<!DOCTYPE html><html><head><metacharset="utf-8"><metaname="viewport"content="width=device-width, initial-scale=1"><metaname="description"content="Handling reactivity in UI via observables."><title>`deka-dom-el` — Observables and reactivity</title><!--<dde:mark type="component" name="metaAuthor" host="this" ssr/>--><metaname="author"content="Jan Andrle"><linktype="text/plain"rel="author"href="https://jaandrle.github.io/humans.txt"><metaname="generator"content="deka-dom-el"><!--<dde:mark type="component" name="metaTwitter" host="this" ssr/>--><metaname="twitter:card"content="summary_large_image"><metaname="twitter:url"content="https://github.com/jaandrle/deka-dom-el"><metaname="twitter:title"content="deka-dom-el"><metaname="twitter:description"content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><metaname="twitter:creator"content="@jaandrle"><!--<dde:mark type="component" name="metaFacebook" host="this" ssr/>--><metaname="og:url"content="https://github.com/jaandrle/deka-dom-el"><metaname="og:title"content="deka-dom-el"><metaname="og:description"content="A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks."><metaname="og:creator"content="@jaandrle"><scriptsrc="https://cdn.jsdelivr.net/npm/shiki"defer=""></script><scripttype="module"src="code.js.js"></script><scriptsrc="https://flems.io/flems.html"type="text/javascript"charset="utf-8"></script><linkrel="stylesheet"href="global.css"></head><body><!--<dde:mark type="component" name="page" host="this" ssr/>--><!--<dde:mark type="component" name="simplePage" host="this" ssr/>--><!--<dde:mark type="component" name="header" host="this" ssr/>--><header><h1>`deka-dom-el` — Observables and reactivity</h1><p>Handling reactivity in UI via observables.</p></header><nav><ahref="https://github.com/jaandrle/deka-dom-el"><svgclass="icon"viewBox="0 0 32 32"><!--<dde:mark type="component" name="iconGitHub" host="parentElement" ssr/>--><pathd="M 16,0.395c -8.836,0 -16,7.163 -16,16c 0,7.069 4.585,13.067 10.942,15.182c 0.8,0.148 1.094,-0.347 1.094,-0.77c 0,-0.381 -0.015,-1.642 -0.022,-2.979c -4.452,0.968 -5.391,-1.888 -5.391,-1.888c -0.728,-1.849 -1.776,-2.341 -1.776,-2.341c -1.452,-0.993 0.11,-0.973 0.11,-0.973c 1.606,0.113 2.452,1.649 2.452,1.649c 1.427,2.446 3.743,1.739 4.656,1.33c 0.143,-1.034 0.558,-1.74 1.016,-2.14c -3.554,-0.404 -7.29,-1.777 -7.29,-7.907c 0,-1.747 0.625,-3.174 1.649,-4.295c -0.166,-0.403 -0.714,-2.03 0.155,-4.234c 0,0 1.344,-0.43 4.401,1.64c 1.276,-0.355 2.645,-0.532 4.005,-0.539c 1.359,0.006 2.729,0.184 4.008,0.539c 3.054,-2.07 4.395,-1.64 4.395,-1.64c 0.871,2.204 0.323,3.831 0.157,4.234c 1.026,1.12 1.647,2.548 1.647,4.295c 0,6.145 -3.743,7.498 -7.306,7.895c 0.574,0.497 1.085,1.47 1.085,2.963c 0,2.141 -0.019,3.864 -0.019,4.391c 0,0.426 0.288,0.925 1.099,0.768c 6.354,-2.118 10.933,-8.113 10.933,-15.18c 0,-8.837 -7.164,-16 -16,-16Z"></path></svg>GitHub</a><ahref="./"title="Introducing a library.">1. Introduction</a><ahref="p02-elements"title="Basic concepts of elements modifications and creations.">2. Elements</a><ahref="p03-events"title="Using not only events in UI declaratively.">3. Events and Addons</a><ahref="p04-observables"title="Handling reactivity in UI via observables."class="current">4. Observables and reactivity</a><ahref="p05-scopes"title="Organizing UI into components">5. Scopes and components</a><ahref="p06-customElement"title="Using custom elements in combinantion with DDE">6. Custom elements</a></nav><main><h2>Using observables to manage reactivity</h2><p>How a program responds to variable data or user interactions is one of the fundamental problems of programming. If we desire to solve the issue in a declarative manner, observables may be a viable approach.</p><divclass="code"data-js="todo"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><codeclass="language-js">// use NPM or for example https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-
</code></div><h3id="h-introducing-observables"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><ahref="#h-introducing-observables"tabindex="-1">#</a> Introducing observables</h3><p>Using observables, we split program logic into the three parts. Firstly (α), we create a variable (constant) representing reactive value. Somewhere later, we can register (β) a logic reacting to the observable value changes. Similarly, in a remaining part (γ), we can update the observable value.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><divid="code-example-1-v1fw44pkzuo"class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><codeclass="language-js">import { O } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
// α — `observable` represents a reactive value
</code></div><script>Flems(document.getElementById("code-example-1-v1fw44pkzuo"),JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { O } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\n// α — `observable` represents areactive value\\nconst observable= O(0);\\n// β — just reacts on observable changes\\nO.on(observable, console.log);\\n// γ — just updates the value\\nconst update= ()=> observable(observable()+1);\\n\\nupdate();\\nconst interval= 5*1000;\\nsetTimeout(clearInterval, 10*interval,\\n\\tsetInterval(update, interval));\\n\"}],\"toolbar\":false}"));</script><p>All this is just an example of <ahref="https://en.wikipedia.org/wiki/Event-driven_programming"title="Wikipedia: Event-driven programming">Event-driven programming</a> and <ahref="https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern"title="Wikipedia: Publish–subscribe pattern">Publish–subscribe pattern</a> (compare for example with <ahref="https://www.npmjs.com/package/fpubsub"title="NPM package: fpubsub">fpubsub library</a>). All three parts can be in some manner independent and still connected to the same reactive entity.</p><p>Observables are implemented in the library as functions. To see current value of observable, just call it without any arguments <code>console.log(observable())</code>. To update the observable value, pass any argument <code>observable('a new value')</code>. For listenning the observable value changes, use <code>O.on(observable, console.log)</code>.</p><p>Similarly to the <code>on</code> function to register DOM events listener. You can use <code>AbortController</code>/<code>AbortSignal</code> to <em>off</em>/stop listenning. In example, you also found the way for representing “live” piece of code computation pattern (derived observable):</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><divid="code-example-1-1ti9ynadhw5c"class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><codeclass="language-js">import { O } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
</code></div><script>Flems(document.getElementById("code-example-1-1ti9ynadhw5c"),JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { O } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst observable= O(0);\\n// computation pattern\\nconst double= O(()=> 2*observable());\\n\\nconst ac= new AbortController();\\nO.on(observable, v=> console.log(\\\"observable\\\", v), { signal: ac.signal });\\nO.on(double, v=> console.log(\\\"double\\\", v), { signal: ac.signal });\\n\\nobservable(observable()+1);\\nconst interval= 5 * 1000;\\nconst id= setInterval(()=> observable(observable()+1), interval);\\nac.signal.addEventListener(\\\"abort\\\",\\n\\t()=> setTimeout(()=> clearInterval(id), 2*interval));\\n\\nsetTimeout(()=> ac.abort(), 3*interval)\\n\"}],\"toolbar\":false}"));</script><h3id="h-observables-and-actions"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><ahref="#h-observables-and-actions"tabindex="-1">#</a> Observables and actions</h3><p><code>O(/* primitive */)</code> allows you to declare simple reactive variables, typically around <em>immutable</em><atitle="Primitive | MDN"href="https://developer.mozilla.org/en-US/docs/Glossary/Primitive">primitive types</a>. However, it may also be necessary to use reactive arrays, objects, or other complex reactive structures.</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><divid="code-example-1-ny81g2qun6o"class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><codeclass="language-js">import { O } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
</code></div><script>Flems(document.getElementById("code-example-1-ihanc6k9v9c"),JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { O } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst todos= O([], {\\n\\tpush(item){\\n\\t\\tthis.value.push(O(item));\\n\\t},\\n\\tpop(){\\n\\t\\tconst removed= this.value.pop();\\n\\t\\tif(removed) O.clear(removed);\\n\\t},\\n\\t[O.symbols.onclear](){ // this covers `O.clear(todos)`\\n\\t\\tO.clear(...this.value);\\n\\t}\\n});\\n\\nimport { el, on } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\n/** @type {ddeElementAddon<HTMLFormElement>} */\\nconst onsubmit= on(\\\"submit\\\", function(event){\\n\\tevent.preventDefault();\\n\\tconst data= new FormData(this);\\n\\tswitch (data.get(\\\"op\\\")){\\n\\t\\tcase \\\"A\\\"/*dd*/:\\n\\t\\t\\tO.action(todos, \\\"push\\\", data.get(\\\"todo\\\"));\\n\\t\\t\\tbreak;\\n\\t\\tcase \\\"E\\\"/*dit*/: {\\n\\t\\t\\tconst last= todos().at(-1);\\n\\t\\t\\tif(!last) break;\\n\\t\\t\\tlast(data.get(\\\"todo\\\"));\\n\\t\\t\\tbreak;\\n\\t\\t}\\n\\t\\tcase \\\"R\\\"/*emove*/:\\n\\t\\t\\tO.action(todos, \\\"pop\\\");\\n\\t\\t\\tbreak;\\n\\t}\\n});\\ndocument.body.append(\\n\\tel(\\\"ul\\\").append(\\n\\t\\tO.el(todos, todos=>\\n\\t\\t\\ttodos.map(textContent=> el(\\\"li\\\", textContent)))\\n\\t),\\n\\tel(\\\"form\\\", null, onsubmit).append(\\n\\t\\tel(\\\"input\\\", { type: \\\"text\\\", name: \\\"todo\\\", placeholder: \\\"Todo’s text\\\" }),\\n\\t\\tel(radio, { textContent: \\\"Add\\\", checked: true }),\\n\\t\\tel(radio, { textContent: \\\"Edit last\\\" }),\\n\\t\\tel(radio, { textContent: \\\"Remove\\\" }),\\n\\t\\tel(\\\"button\\\", \\\"Submit\\\")\\n\\t)\\n);\\ndocument.head.append(\\n\\tel(\\\"style\\\", \\\"form{ display: flex; flex-flow: column nowrap; }\\\")\\n);\\nfunction radio({ textContent, checked= false }){\\n\\treturn el(\\\"label\\\").append(\\n\\t\\tel(\\\"input\\\", { type: \\\"radio\\\", name: \\\"op\\\", value: textContent[0], checked }),\\n\\t\\t\\\" \\\",textContent\\n\\t)\\n}\\n\"}],\"toolbar\":false}"));</script><p>In some way, you can compare it with <ahref="https://react.dev/reference/react/useReducer"title="useReducer hook | React docs">useReducer</a> hook from React. So, the <code>O(<data>, <actions>)</code> pattern creates a store “machine”. We can then invoke (dispatch) registered action by calling <code>O.action(<observable>, <name>, ...<args>)</code> after the action call the observable calls all its listeners. This can be stopped by calling <code>this.stopPropagation()</code> in the method representing the given action. As it can be seen in examples, the “store” value is available also in the function for given action (<code>this.value</code>).</p><h3id="h-reactive-dom-attributes-and-elements"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><ahref="#h-reactive-dom-attributes-and-elements"tabindex="-1">#</a> Reactive DOM attributes and elements</h3><p>There are on basic level two distinc situation to mirror dynamic value into the DOM/UI</p><ol><li>to change some attribute(s) of existing element(s)</li><li>to generate elements itself dynamically – this covers conditions and loops</li></ol><!--<dde:mark type="component" name="example" host="this" ssr/>--><divid="code-example-1-ehcq40v0h5k"class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><codeclass="language-js">import { O } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
const count= O(0);
import { el } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
</code></div><script>Flems(document.getElementById("code-example-1-ehcq40v0h5k"),JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { O } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst count= O(0);\\n\\nimport { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\ndocument.body.append(\\n\\tel(\\\"p\\\", O(()=> \\\"Currently: \\\"+count())),\\n\\tel(\\\"p\\\", { classList: { red: O(()=> count()%2) }, dataset: { count }, textContent: \\\"Attributes example\\\" })\\n);\\ndocument.head.append(\\n\\tel(\\\"style\\\", \\\".red { color: red; }\\\")\\n);\\n\\nconst interval= 5 * 1000;\\nsetTimeout(clearInterval, 10*interval,\\n\\tsetInterval(()=> count(count()+1), interval));\\n\"}],\"toolbar\":false}"));</script><p>To derived attribute based on value of observable variable just use the observable as a value of the attribute (<code>assign(element, { attribute: O('value') })</code>). <code>assign</code>/<code>el</code> provides ways to glue reactive attributes/classes more granularly into the DOM. Just use dedicated build-in attributes <code>dataset</code>, <code>ariaset</code> and <code>classList</code>.</p><p>For computation, you can use the “derived observable” (see above) like <code>assign(element, { textContent: O(()=> 'Hello '+WorldObservable()) })</code>. This is read-only observable its value is computed based on given function and updated when any observable used in the function changes.</p><p>To represent part of the template filled dynamically based on the observable value use <code>O.el(observable, DOMgenerator)</code>. This was already used in the todo example above or see:</p><!--<dde:mark type="component" name="example" host="this" ssr/>--><divid="code-example-2-8r8qappf8mo"class="example"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><codeclass="language-js">import { O } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js";
</code></div><script>Flems(document.getElementById("code-example-2-8r8qappf8mo"),JSON.parse("{\"files\":[{\"name\":\".js\",\"content\":\"import { O } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\nconst count= O(0, {\\n\\tadd(){ this.value= this.value + Math.round(Math.random()*10); }\\n});\\nconst numbers= O([ count() ], {\\n\\tpush(next){ this.value.push(next); }\\n});\\n\\nimport { el } from \\\"https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-observables.js\\\";\\ndocument.body.append(\\n\\tO.el(count, count=> count%2\\n\\t\\t? el(\\\"p\\\", \\\"Last number is odd.\\\")\\n\\t\\t: el()\\n\\t),\\n\\tel(\\\"p\\\", \\\"Lucky numbers:\\\"),\\n\\tel(\\\"ul\\\").append(\\n\\t\\tO.el(numbers, numbers=> numbers.toReversed()\\n\\t\\t\\t.map(n=> el(\\\"li\\\", n)))\\n\\t)\\n);\\n\\nconst interval= 5*1000;\\nsetTimeout(clearInterval, 10*interval, setInterval(function(){\\n\\tO.action(count, \\\"add\\\");\\n\\tO.action(numbers, \\\"push\\\", count());\\n}, interval));\\n\"}],\"toolbar\":false}"));</script><divclass="notice"><!--<dde:mark type="component" name="mnemonic" host="parentElement" ssr/>--><h3id="h-mnemonic"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><ahref="#h-mnemonic"tabindex="-1">#</a> Mnemonic</h3><ul><li><code>O(<value>)</code> — observable: reactive value</li><li><code>O(()=><computation>)</code> — read-only observable: reactive value dependent on calculation using other observables</li><li><code>O.on(<observable>, <listener>[, <options>])</code> — listen to the observable value changes</li><li><code>O.clear(...<observables>)</code> — off and clear observables</li><li><code>O(<value>, <actions>)</code> — observable: pattern to create complex reactive objects/arrays</li><li><code>O.action(<observable>, <action-name>, ...<action-arguments>)</code> — invoke an action for given observable</li><li><code>O.el(<observable>, <function-returning-dom>)</code> — render partial dom structure (template) based on the current observable value</li></ul></div><divclass="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><arel="prev"href="p03-events"title="Using not only events in UI declaratively."><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->Events and Addons (previous)</a><arel="next"href="p05-scopes"title="Organizing UI into components"><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->(next) Scopes and components</a></div></main></body></html>