`Observables` (previously called "Consumables" in Cardboard) are reactive objects that hold a value, notify listeners when the value changes, and can be combined or transformed to create new reactive values. Observables are a core concept in Cardboard, enabling you to build dynamic, reactive applications with minimal boilerplate. > **Note:** In some places, you may still see the term "Consumable"—these are now called "Observables" for clarity and consistency. --- ## Quick Reference ```typescript import { createObservable, state, Observable } from 'cardboard-js/dist/cardboard.js'; // Create an Observable const count = createObservable(0); // or const count = new Observable(0); // or (shorthand) const count = state(0); // Read value console.log(count.value); // Update value count.value = 1; // or count.dispatch(2); // Listen for changes count.changed((newValue, oldValue) => { console.log(`Changed from ${oldValue} to ${newValue}`); }); ``` --- ## Table of Contents - [Quick Reference](#quick-reference) - [What is an Observable?](#what-is-an-observable) - [Creating Observables](#creating-observables) - [Accessing and Updating Values](#accessing-and-updating-values) - [Listening for Changes](#listening-for-changes) - [Example: Reacting to Value Changes](#example-reacting-to-value-changes) - [Computing Observables](#computing-observables) - [Reactivity with Tags](#reactivity-with-tags) - [Built-in Computed Helpers](#built-in-computed-helpers) - [Greater Than](#greater-than) - [Greater Than or Equal To](#greater-than-or-equal-to) --- ## What is an Observable? An **Observable** is an object that: - Holds a value (of any type) - Notifies listeners when its value changes - Can be combined or transformed to create new Observables Observables are used throughout Cardboard for state management, UI reactivity, and more. --- ## Creating Observables There are several ways to create an Observable: ```typescript import { createObservable, Observable, state } from 'cardboard-js/dist/cardboard.js'; // Using the factory function const count = createObservable(42); // Instantiating directly const count2 = new Observable(42); // Using the shorthand (prefered for clarity) const count3 = state(42); ``` --- ## Accessing and Updating Values You can **read** the current value using the `.value` property: ```typescript const current = count.value; ``` Most of the time, when working with [Tags](./Tags.md), you can pass the observable directly, this way the tag updates based on the observable changes: ```typescript const st = state({ count: 0 }); p(`Count: $count`, st); ``` You can **update** the value in two ways: ```typescript count.value = 55; // Triggers listeners count.dispatch(99); // Also triggers listeners ``` Both approaches are equivalent. Whichever fits you best! --- ## Listening for Changes To react to value changes, use the `.changed()` method: ```typescript count.changed((newValue, oldValue) => { console.log(`Count changed from ${oldValue} to ${newValue}`); }); ``` - The callback receives the new value, and (optionally) the previous value. - Listeners are called **after** the value changes. You can add multiple listeners. To remove a listener, keep the returned function: ```typescript const unsubscribe = count.changed(handler); // Later: unsubscribe(); ``` --- ## Example: Reacting to Value Changes ```typescript const temperature = createObservable(25); temperature.changed((newTemp) => { if (newTemp > 30) { console.log("It's getting hot!"); } else { console.log("The temperature is comfortable."); } }); temperature.value = 35; // Logs: "It's getting hot!" ``` --- ## Computing Observables You can create new Observables that derive their value from one or more other Observables. ```typescript import { compute } from 'cardboard-js/dist/cardboard.js'; const weight = createObservable(70); // Create a new Observable that is true if weight > 90 const isOverweight = compute(weight, (value) => value > 90); // Or using the instance method: const isOverweight2 = weight.compute((value) => value > 90); isOverweight.changed((overweight) => { if (overweight) { console.log("You're overweight!"); } else { console.log("You're in a healthy weight range."); } }); weight.value = 95; // Logs: "You're overweight!" ``` You can also compute from multiple Observables: ```typescript const a = state(1); const b = state(2); const sum = compute([a, b], ([aVal, bVal]) => aVal + bVal); sum.changed((total) => { console.log(`Sum is now ${total}`); }); ``` --- ## Reactivity with Tags Observables are especially useful for building reactive UIs with Cardboard. For example, **displaying dynamic text**: ```typescript const error = state(''); p(error); // Later: error.value = 'Wrong password...'; // The text inside the "p" tag updates to show new text ``` It's also usefull for manipulating properties of a tag, like **disabling input**: ```typescript const isDisabled = state(false); input() .disableIf(isDisabled) .classIf(isDisabled, ['box-disabled']); // Later: isDisabled.value = true; // The input becomes disabled and gets the class 'box-disabled' isDisabled.value = false; // The input becomes enabled and removes the class 'box-disabled' ``` Whenever `isDisabled` changes, the input element updates automatically. See [Conditionally Manipulating Tags](./Manipulating-Tags#conditional-element-manipulation-methods) for more details. --- ## Built-in Computed Helpers Cardboard provides helper functions to create Observables for common conditions and comparisons. These work with values or Observables. ### Greater Than ```typescript import { greaterThan } from 'cardboard-js/dist/cardboard.js'; const temperature = state(32); const isTooHot = greaterThan(temperature, 30); // Or const isTooHot = temperature.greaterThan(30); isTooHot.changed((hot) => { if (hot) { console.log("It's too hot outside!"); } else { console.log("The weather is pleasant."); } }); ``` ### Greater Than or Equal To ```typescript import { greaterThanOr } from 'cardboard-js/dist/cardboard.js'; const examScore = state(78); const isPassing = greaterThanOr(examScore, 70); // Or const isPassing = examScore.greaterThanOr(70); isPassing.changed((pass) => { if (pass) { console.log("Congratulations! You passed the exam."); } else { console.log("You need a higher score to pass."); } }); ``` #### Take a look at the [complete list of helpers](https://nombrekeff.github.io/cardboard-js/modules/observables.html) By using these built-in helpers, you can easily create `Observables` that represent common conditions and comparisons in real-world scenarios, making it simple to react to changes in values and provide feedback or perform actions accordingly.