### Manage Subscriptions with DynamicOwner Source: https://context7.com/raquo/airstream/llms.txt Illustrates using DynamicOwner for subscriptions with an activate/deactivate lifecycle, suitable for component mounting/unmounting. Includes examples of dynamic subscription variants. ```scala import com.raquo.airstream.core.EventStream import com.raquo.airstream.ownership.{ManualOwner, DynamicOwner, DynamicSubscription} // --- DynamicOwner (activate/deactivate lifecycle) --- val dynamicOwner = new DynamicOwner(() => println("accessed after kill!")) val dynSub = DynamicSubscription.subscribeFn( dynamicOwner, observable = EventStream.periodic(100), onNext = n => println(s"tick: $n") ) dynamicOwner.activate() // starts all dynamic subscriptions // ... later, e.g. on component unmount: dynamicOwner.deactivate() // pauses all dynamic subscriptions // Can be re-activated: dynamicOwner.activate() // Kill a DynamicSubscription permanently dynSub.kill() // --- DynamicSubscription variants --- // Use .unsafe when activation produces a Subscription with cleanup logic val dynSub2 = DynamicSubscription.unsafe( dynamicOwner, activate = owner => EventStream.periodic(200).addObserver( com.raquo.airstream.core.Observer[Int](n => println(n)) )(owner) ) // subscribeCallback: activate code that does not require cleanup val dynSub3 = DynamicSubscription.subscribeCallback( dynamicOwner, activate = _ => println("activated!") ) ``` -------------------------------- ### Dynamic Subscription Activation and Deactivation Source: https://github.com/raquo/airstream/blob/master/README.md Demonstrates how to create and manage a DynamicSubscription using a DynamicOwner. The activate method is called to start the subscription, and deactivate is used to stop it, with the ability to repeat this process. ```scala val stream: EventStream[Int] = ??? val observer: Observer[Int] = ??? val dynOwner = new DynamicOwner val dynSub = DynamicSubscription.unsafe( dynOwner, activate = (owner: Owner) => stream.addObserver(observer)(owner) ) // Run dynSub's activate method and save the resulting non-dynamic Subscription dynOwner.activate() // Kill the regular Subscription that we saved dynOwner.deactivate() // Run dynSub's activate method again, obtaining a new Subscription dynOwner.activate() // Kill the new Subscription that we saved dynOwner.deactivate() ``` -------------------------------- ### Debug Observable with Multiple Operators Source: https://github.com/raquo/airstream/blob/master/README.md This example demonstrates chaining multiple debugging operators to an observable. Use `debugWithName` to set a prefix for logs, `debugLogEvents` to conditionally log events, `debugSpyStarts` to execute a callback when the observable starts, and `debugBreakErrors` to set a JavaScript breakpoint when an error occurs. Replace the original observable with the debugged one in your observer setup. ```scala val stream: EventStream[Int] = ??? val useJsLogger: Boolean = false val debugStream = stream .debugWithName("MyStream") // optional: use this prefix when logging below .debugLogEvents(when = _ < 0, useJsLogger) // optional: only log negative numbers .debugSpyStarts(topoRank => ???) .debugBreakErrors() // Before: stream.addObserver(obs) // After: when debugging, replace with: debugStream.addObserver(obs) ``` -------------------------------- ### Var Transaction Delay Example Source: https://github.com/raquo/airstream/blob/master/README.md Demonstrates the difference in Var value retrieval when reading during an active transaction versus outside of one. Use `now()` inside a new transaction or `update` to reliably get the new value. ```scala val myVar = Var(0) println("Start") myVar.set(1) println(s"After set: ${myVar.now()}") myVar.update(_ + 1) println(s"After update: ${myVar.now()}") Transaction { _ => println(s"After trx: ${myVar.now()}") } println("Done") ``` -------------------------------- ### FetchStream GET Request Source: https://github.com/raquo/airstream/blob/master/README.md Initiate a GET request using FetchStream.get to retrieve the response body as an EventStream of strings. Customize request options like redirect and referrer policy, and provide an abortStream for cancellation. ```scala FetchStream.get( url, _.redirect(_.follow), _.referrerPolicy(_.`no-referrer`), _.abortStream(...) ) // EventStream[String] of response body ``` -------------------------------- ### FRP Diamond Case Example Source: https://github.com/raquo/airstream/blob/master/README.md Illustrates a typical FRP diamond case where an observable depends on multiple other observables that share a common ancestor. This example highlights potential glitch scenarios in other libraries. ```Scala val numbers: EventStream[Int] = ??? val isPositive: EventStream[Boolean] = numbers.map(_ > 0) val doubledNumbers: EventStream[Int] = numbers.map(_ * 2) val combinedStream: EventStream[(Int, Boolean)] = doubledNumbers.combineWith(isPositive) combinedStream.addObserver(combinedStreamObserver)(owner) ``` -------------------------------- ### Split Try Operator Example in Laminar Source: https://github.com/raquo/airstream/blob/master/README.md Demonstrates using the splitTry operator to conditionally render UI based on the success or failure of a Try[User] signal. Requires importing the necessary Airstream and Laminar modules. ```scala val userTrySignal: Signal[Try[User]] = ??? div( child <-- userTrySignal.splitTry( success = (initialUser: User, userSignal: Signal[User]) => div("User name: ", text <-- userSignal.map(_.name)), failure = (initialErr: Throwable, errSignal: Signal[Throwable]) => div("Something is wrong: ", text <-- errSignal.map(_.getMessage)) ) ) ``` -------------------------------- ### Perform GET Request with AjaxStream Source: https://github.com/raquo/airstream/blob/master/README.md Use AjaxStream.get to initiate an HTTP GET request. The stream emits the XMLHttpRequest object, which can be mapped to extract the response text. Requests are made when the stream starts. ```scala AjaxStream .get("/api/kittens") // EventStream[dom.XMLHttpRequest] .map(req => req.responseText) // EventStream[String] ``` -------------------------------- ### Define CustomSignalSource Parameters Source: https://github.com/raquo/airstream/blob/master/README.md This illustrates the constructor for CustomSignalSource, highlighting the parameters for getting the initial value and configuring the source's lifecycle, including setting current values and accessing current state. ```Scala class CustomSignalSource[A] ( getInitialValue: => Try[A], makeConfig: (SetCurrentValue[A], GetCurrentValue[A], GetStartIndex, GetIsStarted) => CustomSource.Config ) ``` -------------------------------- ### Observe Signal to Get StrictSignal Source: https://github.com/raquo/airstream/blob/master/README.md Starts a Signal by adding a no-op observer, returning an OwnedSignal (a StrictSignal) that safely exposes its current value via `now()` and `tryNow()`. Requires an Owner. ```scala observe on it ``` -------------------------------- ### flatMapSwitch Example Source: https://github.com/raquo/airstream/blob/master/README.md Use flatMapSwitch to flatten an observable of streams, switching to a new inner stream whenever the parent stream emits a new event. This operator kills the previous inner stream and starts mirroring the new one. ```scala val parentStream: EventStream[A] = ??? def makeInnerStream(ev: A): EventStream[B] = ??? val flatStream: EventStream[B] = parentStream.flatMapSwitch(ev => makeInnerStream(ev)) ``` -------------------------------- ### flatMapMerge Example Source: https://github.com/raquo/airstream/blob/master/README.md Use flatMapMerge to flatten an observable of streams, merging events from all inner streams. Unlike flatMapSwitch, it continues listening to all previously created inner streams. ```scala val parentBus: EventBus[A] = ??? val parentStream: EventStream[A] = parentBus.events def makeInnerStream(ev: A): EventStream[B] = ??? val flatStream: EventStream[B] = parentStream.flatMapMerge(ev => makeInnerStream(ev)) parentBus.emit(a1) parentBus.emit(a2) // Now flatStream re-emits the events from both // makeInnerStream(a1) and makeInnerStream(a2), // assuming it has any observers, of course. ``` -------------------------------- ### Create LocalStorage Var with Custom Codec Source: https://github.com/raquo/airstream/blob/master/README.md For complex data types, use .withCodec to define custom encoding and decoding functions. This allows storing non-string data by serializing it, for example, using JSON. ```scala val fooVar: WebStorageVar[Foo] = WebStorageVar .localStorage(key = "foo", syncOwner = None) .withCodec( encode = Foo.toJson, decode = Foo.fromJson, default = Success(Foo(1, "name")) ) ``` -------------------------------- ### EventStream Map Chain Example Source: https://github.com/raquo/airstream/blob/master/README.md Demonstrates a chain of EventStreams using the map operator. Streams remain inactive until an observer is added to one of them, triggering a cascade of activations up the chain. ```scala val foo: EventStream[Foo] = ??? val bar: EventStream[Bar] = foo.map(fooToBar) val baz: EventStream[Baz] = bar.map(barToBaz) val qux: EventStream[Qux] = baz.map(bazToQux) val rap: EventStream[Rap] = qux.map(quxToRap) baz.addObserver(bazObserver) ``` -------------------------------- ### Signal Creation Source: https://context7.com/raquo/airstream/llms.txt Demonstrates various ways to create Signal instances, including constant values, futures, promises, and custom sources. ```APIDOC ## Signal Creation ```scala import com.raquo.airstream.core.Signal import com.raquo.airstream.ownership.ManualOwner // Constant signal (never updates) val always5: Signal[Int] = Signal.fromValue(5) // From a Future (starts as None, becomes Some when the future resolves) import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future val fromFuture: Signal[Option[String]] = Signal.fromFuture(Future.successful("done")) // From a Scala.js Promise with a fallback initial value import scala.scalajs.js val fromPromise: Signal[String] = Signal.fromJsPromise( js.Promise.resolve[String]("resolved"), initial = "loading" ) // From a custom source (e.g. wrapping a JS interval) val counter: Signal[Int] = Signal.fromCustomSource( initial = scala.util.Success(0), start = (setValue, getValue, _, _) => { var n = getValue().getOrElse(0) js.timers.setInterval(1000) { n += 1; setValue(scala.util.Success(n)) } () }, stop = _ => () ) ``` ``` -------------------------------- ### Create and Use EventBus Source: https://context7.com/raquo/airstream/llms.txt Demonstrates creating an EventBus, emitting values, and piping streams into it. Requires an Owner for stream piping. ```scala import com.raquo.airstream.eventbus.EventBus import com.raquo.airstream.ownership.ManualOwner val bus = new EventBus[String] // Read side val stream = bus.events // or bus.stream // Write side bus.emit("hello") bus.writer.onNext("world") bus.emitTry(scala.util.Success("try-value")) // Batch-emit to multiple buses in the same transaction val bus2 = new EventBus[Int] EventBus.emit(bus -> "a", bus2 -> 1) // Pipe another stream into the bus (while an owner is alive) implicit val owner: ManualOwner = new ManualOwner val source = EventStream.periodic(500).map(_.toString) bus.writer.addSource(source)(owner) // Use as Observer val obs = bus.toObserver // Observer[String] obs.onNext("via observer") ``` -------------------------------- ### Create and Compose Observers Source: https://context7.com/raquo/airstream/llms.txt Shows how to create simple and complex observers, including handling errors and composing them using methods like contramap and filter. Requires Airstream core imports. ```scala import com.raquo.airstream.core.Observer import com.raquo.airstream.core.EventStream import com.raquo.airstream.ownership.ManualOwner import scala.util.{Success, Failure} // Simple observer val printer = Observer[Int](n => println(s"value: $n")) // Observer that handles both values and errors val withErrors = Observer.withRecover[Int]( onNext = n => println(s"ok: $n"), onError = { case e: ArithmeticException => println(s"math error: ${e.getMessage}") } ) // Observer from Try handler val fromTry = Observer.fromTry[String] { case Success(v) => println(s"success: $v") case Failure(e) => println(s"failure: ${e.getMessage}") } // contramap: adapt the observer's input type val stringObserver: Observer[String] = printer.contramap(_.length) stringObserver.onNext("hello") // printer sees 5 // contracollect: only forward when partial function matches val positiveOnly: Observer[Int] = printer.contracollect { case n if n > 0 => n } // filter: forward only passing events val evenOnly: Observer[Int] = printer.filter(_ % 2 == 0) // delay: defer observer call by N ms (not ownership-aware, use with care) val delayed: Observer[Int] = printer.delay(500) // Combine observers: fan-out to all val combined = Observer.combine(printer, evenOnly) // Subscribe to a stream implicit val owner: ManualOwner = new ManualOwner val stream = EventStream.fromSeq(List(1, 2, 3)) stream.addObserver(printer)(owner) ``` -------------------------------- ### Var Creation and Reading Source: https://context7.com/raquo/airstream/llms.txt How to create a Var, which is a mutable state container, and read its current value. ```APIDOC ## Var Creation and Reading ```scala import com.raquo.airstream.state.Var val counter = Var(0) println(counter.now()) // 0 ``` ``` -------------------------------- ### Handle Case with Direct Pattern Matching Source: https://github.com/raquo/airstream/blob/master/README.md Shows how to use `handleCase` directly for arbitrary pattern matching, such as extracting a `userId` immediately. ```scala handleCase { case UserPage(userId) => userId } { (initialUserId, userIdSignal) => div(...) } ``` -------------------------------- ### Manage Subscriptions with ManualOwner Source: https://context7.com/raquo/airstream/llms.txt Demonstrates manual control over subscriptions using ManualOwner, including creating subscriptions and manually killing them or all subscriptions owned by the owner. ```scala import com.raquo.airstream.core.EventStream import com.raquo.airstream.ownership.{ManualOwner, DynamicOwner, DynamicSubscription} // --- ManualOwner --- val owner = new ManualOwner val stream = EventStream.fromSeq(List(1, 2, 3)) val sub = stream.foreach(println)(owner) // Subscription sub.kill() // kill one subscription manually // or: owner.killSubscriptions() // kill all subscriptions owned by this owner ``` -------------------------------- ### Get Observable Topological Rank Source: https://github.com/raquo/airstream/blob/master/README.md Use `debugTopoRank` to retrieve the topological rank of an observable. This operator does not affect the observable or create new ones. ```scala observable.debugTopoRank ``` -------------------------------- ### Using EventBus as a Source in Laminar Source: https://github.com/raquo/airstream/blob/master/README.md Demonstrates how an EventBus can be used as a Source[String] in Laminar, with implicit conversion handling. ```Scala val textBus = new EventBus[String] div(value <-- textBus.events) div(value <-- textBus) // Also works because this <-- accepts Source[String] ``` -------------------------------- ### FetchStream Raw GET Request Source: https://github.com/raquo/airstream/blob/master/README.md Obtain a stream of raw `dom.Response` objects by using FetchStream.raw.get. This provides direct access to the network response. ```scala FetchStream.raw.get(url) // EventStream[dom.Response] ``` -------------------------------- ### Var Subscription Source: https://context7.com/raquo/airstream/llms.txt Illustrates how to subscribe to the signal derived from a Var to observe its changes. ```APIDOC ## Subscription ```scala import com.raquo.airstream.ownership.ManualOwner implicit val owner: ManualOwner = new ManualOwner counter.signal.foreach(n => println(s"counter = $n"))(owner) ``` ``` -------------------------------- ### Create EventStream from Single Value Source: https://context7.com/raquo/airstream/llms.txt Creates an EventStream that emits a single value. Set emitOnce = true to ensure it only emits on the first subscription start. ```scala // Emit a single value once (emitOnce = true means only the first start) val once: EventStream[String] = EventStream.fromValue("hello", emitOnce = true) ``` -------------------------------- ### Subscribe to Signals in Airstream Source: https://context7.com/raquo/airstream/llms.txt Demonstrates how to subscribe to a Signal using .observe, which makes the current value accessible via .now(). Requires an owner for managing the subscription lifecycle. ```scala import com.raquo.airstream.ownership.ManualOwner // --- Subscription (observe makes current value accessible via .now()) implicit val owner: ManualOwner = new ManualOwner val observed = nameSignal.observe(owner) // OwnedSignal[String] println(observed.now()) // "Alice" ``` -------------------------------- ### Create and Map Signal Source: https://github.com/raquo/airstream/blob/master/README.md Demonstrates creating a Signal from an EventStream with an initial value and then mapping it to another Signal. The initial value and mapping are evaluated lazily. ```scala val fooStream: EventStream[Foo] = ??? val fooSignal: Signal[Foo] = fooStream.startWith(myFoo) val barSignal: Signal[Bar] = fooSignal.map(fooToBar) ``` -------------------------------- ### Declare Signals in Scala Source: https://github.com/raquo/airstream/blob/master/README.md Declare two signals, numSignal and fooSignal, where fooSignal is derived from numSignal using a map operation. This is a common setup for demonstrating signal behavior. ```scala val numSignal: Signal[Int] = ??? val fooSignal: Signal[Foo] = numSignal.map(Foo(_)) ``` -------------------------------- ### Var Writing Source: https://context7.com/raquo/airstream/llms.txt Demonstrates methods for updating the value of a Var, including direct setting, updating based on current value, and setting an error state. ```APIDOC ## Var Writing ```scala // Write counter.set(5) // now() == 5 counter.update(_ + 1) // now() == 6 counter.setError(new Exception("oops")) // signal is in failed state ``` ``` -------------------------------- ### Create EventStream from Java Publisher Source: https://github.com/raquo/airstream/blob/master/README.md Converts a Java Flow.Publisher into an Airstream EventStream. The stream subscribes to the publisher when started and cancels when stopped. Useful for interoperating with other streaming APIs like FS2. ```scala import cats.effect.unsafe.implicits._ // imports implicit IORuntime EventStream.fromPublisher(fs2Stream.unsafeToPublisher()) ``` -------------------------------- ### Using delayWithStatus and flatMapWithStatus Source: https://context7.com/raquo/airstream/llms.txt Demonstrates how to use `delayWithStatus` for delayed emissions and `flatMapWithStatus` for asynchronous operations that return an EventStream. Requires importing `Status`, `Pending`, and `Resolved` from `airstream.status`. ```scala import com.raquo.airstream.core.EventStream import com.raquo.airstream.status.{Status, Pending, Resolved} import com.raquo.airstream.ownership.ManualOwner val clicks: EventStream[Unit] = EventStream.fromValue(()) // delayWithStatus: emits Pending(input) immediately, then Resolved(input, output) after delay val withStatus: EventStream[Status[Unit, Unit]] = clicks.delayWithStatus(ms = 300) // flatMapWithStatus val queries: EventStream[String] = EventStream.fromValue("foo") val statusStream: EventStream[Status[String, List[String]]] = queries.flatMapWithStatus { q => EventStream.fromFuture( scala.concurrent.Future.successful(List(s"result: $q")) )(scala.concurrent.ExecutionContext.Implicits.global) } implicit val owner: ManualOwner = new ManualOwner statusStream.foreach { case Status.Pending(input) => println(s"loading for: $input") case Status.Resolved(input, output) => println(s"done: $output for $input") }(owner) ``` -------------------------------- ### Handle Case with Aliases Source: https://github.com/raquo/airstream/blob/master/README.md Demonstrates `handleValue` and `handleType` as convenient aliases for `handleCase` with specific pattern matching scenarios. ```scala // handleValue(LoginPage)(div(...)) handleCase { case LoginPage => () } { (_, _) => div(...) } // handleType[UserPage] { // (initialUserPage, userPageSignal) => div(...) // } handleCase { case up: UserPage => up } { (initialUserPage, userPageSignal) => div(...) } ``` -------------------------------- ### Create Signals in Airstream Source: https://context7.com/raquo/airstream/llms.txt Demonstrates various ways to create Signals, including from a static value, a Future, a JS Promise, and a custom source with a JS interval. ```scala import com.raquo.airstream.core.Signal import com.raquo.airstream.ownership.ManualOwner // --- Creation --- // Constant signal (never updates) val always5: Signal[Int] = Signal.fromValue(5) // From a Future (starts as None, becomes Some when the future resolves) import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future val fromFuture: Signal[Option[String]] = Signal.fromFuture(Future.successful("done")) // From a Scala.js Promise with a fallback initial value import scala.scalajs.js val fromPromise: Signal[String] = Signal.fromJsPromise( js.Promise.resolve[String]("resolved"), initial = "loading" ) // From a custom source (e.g. wrapping a JS interval) val counter: Signal[Int] = Signal.fromCustomSource( initial = scala.util.Success(0), start = (setValue, getValue, _, _) => { var n = getValue().getOrElse(0) js.timers.setInterval(1000) { n += 1; setValue(scala.util.Success(n)) } () }, stop = _ => () ) ``` -------------------------------- ### Implement DOM Event Stream with CustomStreamSource Source: https://github.com/raquo/airstream/blob/master/README.md This example shows how Airstream's DomEventStream.apply uses CustomStreamSource to wrap DOM event listeners. It registers and unregisters event handlers to manage resources effectively. ```Scala def apply[Ev <: dom.Event]( eventTarget: dom.EventTarget, eventKey: String, useCapture: Boolean = false ): EventStream[Ev] = { CustomStreamSource[Ev]( (fireValue, fireError, getStartIndex, getIsStarted) => { val eventHandler: js.Function1[Ev, Unit] = fireValue CustomSource.Config( onStart = () => { eventTarget.addEventListener(eventKey, eventHandler, useCapture) }, onStop = () => { eventTarget.removeEventListener(eventKey, eventHandler, useCapture) } ) }) } ``` -------------------------------- ### Scala Example of Stream Error Propagation Source: https://github.com/raquo/airstream/blob/master/README.md Illustrates a scenario where an error in a parent stream cannot be recovered within a mapped function. This highlights the limitations of conventional streaming libraries in handling errors within nested observables. ```scala object OtherModule { def doubled(parentStream: EventStream[Double]): EventStream[Double] = { parentStream.map(num => num * 2) } } // ---- import OtherModule.doubled val stream1: EventStream[Double] = ??? val invertedStream: EventStream[Double] = stream1.map(num => 1 / num) doubled(invertedStream).foreach(dom.console.log(_)) ``` -------------------------------- ### Expanded splitOne Logic for splitMatchOne Source: https://github.com/raquo/airstream/blob/master/README.md Illustrates the underlying splitOne operator expansion of the splitMatchOne macro, showing how type-specific handlers are mapped and signals are transformed. ```scala val elementSignal: Signal[HtmlElement] = pageSignal .map { // condition => (handlerIndex, handlerInput) case HomePage => (0, ()) case LoginPage => (1, ()) case up: UserPage => (2, up) } .splitOne(key = _._1) { ( handlerIndex: Int, handlerIndexAndInput: (Int, Page | Unit), signalOfIndexAndInput: Signal[(Int, Page | Unit)] ) => if (handlerIndex == 0) { // handleValue(HomePage) div(h1("Home page")) } else if (handlerIndex == 1) { // handleValue(LoginPage) div(h1("Login page")) } else if (handlerIndex == 2) { // handleType[UserPage] { ... } val initialUserPage: UserPage = handlerIndexAndInput._2.asInstanceOf[UserPage] val userPageSignal: Signal[UserPage] = signalOfIndexAndInput.map(_._2.asInstanceOf[UserPage]) div( h1("User #", text <-- userPageSignal.map(_.id)) ) } } ``` -------------------------------- ### Signal Subscription Source: https://context7.com/raquo/airstream/llms.txt Shows how to subscribe to a Signal to observe its values over time using an owner. ```APIDOC ## Signal Subscription ```scala // Subscription (observe makes current value accessible via .now()) implicit val owner: ManualOwner = new ManualOwner val observed = nameSignal.observe(owner) // OwnedSignal[String] println(observed.now()) // "Alice" ``` ``` -------------------------------- ### Derived Var Example with LazyEvaluation Source: https://github.com/raquo/airstream/blob/master/README.md Illustrates the creation and usage of a `LazyDerivedVar` using `zoomLazy`. Updates to either the parent or derived Var are reflected in both due to the two-way link. The derived value is evaluated lazily upon access. ```scala case class FormData(num: Int, str: String) val formDataVar = Var(FormData(0, "a")) val strVar = formDataVar.zoomLazy(_.str)((formData, newStr) => formData.copy(str = newStr)) // strVar.now() == "a" formDataVar.update(_.copy(str = "b")) // formDataVar.now() == FormData(0, "b") // strVar.now() == "b" strVar.set("c") // formDataVar.now() == FormData(0, "c") // strVar.now() == "c" ``` -------------------------------- ### Logging All Observable Events with Debug Operators Source: https://context7.com/raquo/airstream/llms.txt Use debugLogAll to log starts, stops, events, and eval-from-parent for an observable. Rename observables with debugWithName for clearer log output. Set useJsLogger to true to use the browser's console.log. ```scala import com.raquo.airstream.core.EventStream import com.raquo.airstream.state.Var import com.raquo.airstream.ownership.ManualOwner val src = Var(0) // debugLogAll: log starts, stops, events, and eval-from-parent val debugged: Signal[Int] = src.signal .debugWithName("MySignal") // rename for log output .debugLogAll(useJsLogger = false) ``` -------------------------------- ### Create Observer with ContracollectOpt Source: https://github.com/raquo/airstream/blob/master/README.md Use `contracollectOpt` for APIs that return Options. It combines mapping to an Option and filtering out None values. ```scala contracollectOpt(project) ``` -------------------------------- ### Create DOM Event Stream in Scala Source: https://github.com/raquo/airstream/blob/master/README.md Create a stream that emits DOM events, such as mouse clicks, from a specified element. The stream automatically registers and removes the event listener when started and stopped. You must manually specify the event type. ```scala val element: dom.Element = ??? DomEventStream[dom.MouseEvent](element, "click") // EventStream[dom.MouseEvent] ``` -------------------------------- ### Observer Error Handling Example Source: https://github.com/raquo/airstream/blob/master/README.md Demonstrates how errors thrown by observer callbacks are wrapped. If the callback throws an exception while processing an incoming error, it's wrapped in ObserverErrorHandlingError. If it throws while processing an event, it's wrapped in ObserverError and sent to the observer's onError method. ```scala val sourceObs = Observer.fromTry[Int](println) val derivedObs = sourceObs.contramap[Int] { value => if (value >= 0) 1 else throw new Exception("negative!") } derivedObs.onNext(1) // prints "Success(1)" derivedObs.onNext(-1) // prints "Failure(ObserverError: java.lang.Exception: negative!)" // no errors are sent into unhandled because `println` is a total function, it handles them all. ``` -------------------------------- ### Split Match Sequence for Rendering Items Source: https://github.com/raquo/airstream/blob/master/README.md Illustrates `splitMatchSeq` for rendering sequences of items, where each item is handled differently based on its type (e.g., `Stock` or `FxRate`). Requires defining `Item` trait and case classes. ```scala trait Item { val id: String } case class Stock(ticker: String, currency: String, value: Double) { override val id: String = ticker } case class FxRate(currency1: String, currency2: String, rate: Double) { override val id: String = currency1 + "-" + currency2 } val itemsSignal: Signal[Seq[Item]] = ??? val elementsSignal: Signal[Seq[HtmlElement]] = modelsSignal .splitMatchSeq(_.id) .handleType[Stock] { (initialStock, stockSignal) => div( initialStock.id + ": ", text <-- stockSignal.map(_.value), " " + initialStock.currency ) } .handleType[FxRate] { (initialRate, rateSignal) => div(initialRate.id, ": ", text <-- rateSignal.map(_.rate)) } .toSignal children <-- elementsSignal // in Laminar ``` -------------------------------- ### Create EventBus and Trigger Events Source: https://github.com/raquo/airstream/blob/master/README.md Instantiate an `EventBus` to manually trigger events. Use the `events` stream to observe emitted events and the `writer` to trigger them. Events are always emitted in a new transaction. ```scala val eventBus = new EventBus[Foo] val barWriter: WriteBus[Bar] = eventBus.writer .filterWriter(isGoodFoo) .contramapWriter(barToFoo) ``` -------------------------------- ### Var.set vs Var.update in Airstream Source: https://github.com/raquo/airstream/blob/master/README.md Demonstrates how `Var#set` schedules a transaction that is not immediately executed, while `Var#update` schedules a transaction that evaluates its modification when the transaction runs, ensuring it uses the freshest state. ```scala val logVar: Var[List[Event]] = ??? stream.foreach { ev => logVar.set(logVar.now() :+ ev) logVar.set(logVar.now() :+ ev) println(logVar.now()) // NONE of the logVar.now() calls here will contain any `ev` // because they are all executed before the .set transaction executes. // Because of this, after all of the transactions are executed, // logVar will only contain one instance of `ev`, not two. } ``` ```scala val logVar = Var(List[Event]()) stream.foreach { ev => logVar.update(_ :+ ev) logVar.update(_ :+ ev) // After both transactions execute, logVar will have two `ev`-s in it } ``` -------------------------------- ### Derive Vars using zoom and bimap Source: https://context7.com/raquo/airstream/llms.txt Illustrates creating derived Vars using zoom for bidirectional access to nested data structures and bimap for transforming the Var's value bidirectionally. ```scala import com.raquo.airstream.state.Var // --- zoom (derived Var, bidirectional lens) --- case class User(name: String, age: Int) val userVar = Var(User("Alice", 30)) val nameVar: Var[String] = userVar.zoom(_.name)((user, n) => user.copy(name = n)) nameVar.set("Bob") // userVar.now() == User("Bob", 30) // --- bimap --- val jsonVar = Var("{\"x\":1}") val parsed: Var[Map[String, Int]] = jsonVar.bimap( getThis = s => io.circe.parser.decode[Map[String,Int]](s).getOrElse(Map.empty), getParent = m => io.circe.syntax.EncoderOps(m).asJson.noSpaces ) ``` -------------------------------- ### Create and Read Vars in Airstream Source: https://context7.com/raquo/airstream/llms.txt Introduces Airstream's Var, a reactive state container that is a Signal you can write to. Shows basic creation and reading of its current value. ```scala import com.raquo.airstream.state.Var // --- Create and read --- val counter = Var(0) println(counter.now()) // 0 ``` -------------------------------- ### Convert Observable to Signal with Initial Value Source: https://github.com/raquo/airstream/blob/master/README.md Use `toSignalIfStream` to convert an Observable to a Signal. Provide an initial value using `startWith` for stream-like Observables. ```scala observable.toSignalIfStream(_.startWith("")) ``` -------------------------------- ### Signal Transformation Source: https://context7.com/raquo/airstream/llms.txt Illustrates common transformations applied to Signals, such as mapping, debouncing, accumulating, combining, and sampling. ```APIDOC ## Signal Transformation ```scala val nameSignal: Signal[String] = Signal.fromValue("Alice") val upper = nameSignal.map(_.toUpperCase) // Signal[String]: "ALICE" // compose the underlying updates stream (e.g. add debounce) val debounced = nameSignal.composeUpdates(_.debounce(200)) // scanLeft over updates produces an accumulating Signal val lengths: Signal[Int] = nameSignal.updates.scanLeft(0)((acc, _) => acc + 1) // Access .updates to get only the changes (not initial value) val changes: EventStream[String] = nameSignal.updates // combineWith: re-emits whenever either signal changes val ageSignal: Signal[Int] = Signal.fromValue(30) val combined: Signal[(String, Int)] = nameSignal.combineWith(ageSignal) // withCurrentValueOf: sample ageSignal whenever nameSignal updates val sampled: Signal[(String, Int)] = nameSignal.withCurrentValueOf(ageSignal) ``` ``` -------------------------------- ### Var Writer and Updater Observers Source: https://context7.com/raquo/airstream/llms.txt Shows how to use Observer types to write to or update a Var. ```APIDOC ## Var writer Observer ```scala import com.raquo.airstream.core.Observer // writer Observer val w: Observer[Int] = counter.writer w.onNext(10) // counter.now() == 10 // updater Observer val appender: Observer[Int] = counter.updater((acc, delta) => acc + delta) appender.onNext(3) // counter.now() == 13 ``` ``` -------------------------------- ### Coordinating Observer Additions in Transactions Source: https://context7.com/raquo/airstream/llms.txt Transaction.onStart.shared is used internally and in advanced Laminar patterns to coordinate observer additions when restarting a signal. Observers added within this block share a common restart transaction. ```scala // onStart.shared: coordinate observer additions when restarting signal.updates // (used internally and in advanced Laminar patterns) Transaction.onStart.shared { // observers added inside here share a common restart transaction x.signal.foreach(_ => ())(new com.raquo.airstream.ownership.ManualOwner) y.signal.foreach(_ => ())(new com.raquo.airstream.ownership.ManualOwner) } ``` -------------------------------- ### Configure Duplicate Key Warnings Source: https://github.com/raquo/airstream/blob/master/README.md Shows how to disable duplicate key warnings globally or for individual split observables. This is useful for debugging or performance optimization. ```scala // Disable duplicate key warnings by default DuplicateKeysConfig.setDefault(DuplicateKeysConfig.noWarnings) ``` ```scala // Disable warnings for just one split observable stream.split(_.id, duplicateKeys = DuplicateKeysConfig.noWarnings)(...) ``` -------------------------------- ### TransferableSubscription API Source: https://github.com/raquo/airstream/blob/master/README.md Illustrates the basic structure of a TransferableSubscription, which allows moving a subscription between active DynamicOwners without deactivation and re-activation. Note the absence of Owner access in the activate method for safety. ```scala class TransferableSubscription( activate: () => Unit, deactivate: () => Unit ) { def setOwner(nextOwner: DynamicOwner): Unit def clearOwner(): Unit } ``` -------------------------------- ### Var Zoom Source: https://context7.com/raquo/airstream/llms.txt Explains how to create derived Vars using zoom, which acts as a bidirectional lens for nested data structures. ```APIDOC ## zoom (derived Var, bidirectional lens) ```scala case class User(name: String, age: Int) val userVar = Var(User("Alice", 30)) val nameVar: Var[String] = userVar.zoom(_.name)((user, n) => user.copy(name = n)) nameVar.set("Bob") // userVar.now() == User("Bob", 30) ``` ``` -------------------------------- ### Sample Signal Value from Stream (Simplified) Source: https://github.com/raquo/airstream/blob/master/README.md A simplified version of sampling a Signal's current value when an EventStream emits, useful when the EventStream's last event is not needed. The resulting stream emits only when the source EventStream emits. ```scala stream.sample(signal).map(signalCurrentValue => ???) ``` -------------------------------- ### FetchStream with Custom Codec Source: https://github.com/raquo/airstream/blob/master/README.md Configure FetchStream with custom request and response codecs using FetchStream.withCodec. This allows for specific serialization and deserialization logic for network requests and responses. ```scala val Fetch = FetchStream.withCodec(encodeRequest, decodeResponse) Fetch.post(url, _.body(myRequest)) // EventStream[MyResponse] ``` -------------------------------- ### Simulated update routing for bimap Source: https://github.com/raquo/airstream/blob/master/README.md Illustrates how updates to a derived Var (like `jsonVar`) are processed by first converting the new value and then setting both the parent and derived Vars. Proper error handling is omitted for brevity. ```scala // Real implementation does proper error handling val newFoo = Foo.fromJson(newJsonStr) Var.set( fooVar -> newFoo, jsonVar -> Foo.toJson(newFoo) ) ``` -------------------------------- ### Create EventStream with Callback Source: https://github.com/raquo/airstream/blob/master/README.md Use `EventStream.withCallback` to create a stream and a callback. The stream only emits if it has observers. Call the callback to pass values to the stream. ```scala val (stream, callback) = EventStream.withCallback[Int] callback(1) // nothing happens because stream has no observers stream.foreach(println) callback(2) // `2` will be printed ``` -------------------------------- ### Track Request Status and Loading State Source: https://github.com/raquo/airstream/blob/master/README.md Uses `flatMapWithStatus` to track the status of network requests and derive a loading indicator. Requires a `FetchStream` implementation. ```scala val requestS: EventStream[Request] = ??? type Response = String // but it could be something else val responseS: EventStream[Status[Request, Response]] = requestS.flatMapWithStatus { request => // returns EventStream[Response] FetchStream.get(request.url, request.options) } val isLoadingS: EventStream[Boolean] = responseS.map(_.isPending) val textS: EventStream[String] = responseS.foldStatus( resolved = _.toString, pending = _ => "Loading..." ) // Example usage from Laminar: div( child(img(src("spinner.gif"))) <-- isLoadingS, text <-- textS ) // Or, perhaps more realistically: div( child <-- responseS.splitStatus( (resolved, _) => div("Response: " + resolved.output.toString), (pending, _) => div(img(src("spinner.gif")), "Loading ...") ) ) ``` -------------------------------- ### Inefficient Dynamic List Rendering with `map` Source: https://github.com/raquo/airstream/blob/master/README.md This naive approach using `map` recreates all DOM nodes from scratch on every update, which is highly inefficient compared to using the `split` operator. It also lacks the ability to access initial data or listen for changes within individual items. ```scala case class Foo(id: String, version: Int) def renderFoo(foo: Foo): Div = { div( "foo id: " + foo.id, "last seen foo with this id: " + foo.toString ) } val inputSignal: Signal[List[Foo]] = ??? val outputSignal: Signal[List[Div]] = inputSignal.map(_.map(renderFoo)) ``` -------------------------------- ### Subscribe to Var Updates in Airstream Source: https://context7.com/raquo/airstream/llms.txt Demonstrates subscribing to the Signal derived from a Var using .signal.foreach. This allows observing changes to the Var's value over time, managed by an owner. ```scala import com.raquo.airstream.state.Var import com.raquo.airstream.ownership.ManualOwner // --- Subscription --- implicit val owner: ManualOwner = new ManualOwner counter.signal.foreach(n => println(s"counter = $n"))(owner) ``` -------------------------------- ### Var Bimap Source: https://context7.com/raquo/airstream/llms.txt Demonstrates the bimap function for creating a Var that transforms values bidirectionally between two types. ```APIDOC ## bimap ```scala val jsonVar = Var("{\"x\":1}") val parsed: Var[Map[String, Int]] = jsonVar.bimap( getThis = s => io.circe.parser.decode[Map[String,Int]](s).getOrElse(Map.empty), getParent = m => io.circe.syntax.EncoderOps(m).asJson.noSpaces ) ``` ``` -------------------------------- ### Convert EventStream to Signal Source: https://context7.com/raquo/airstream/llms.txt Converts an EventStream to a Signal using startWith. This provides an initial value, making the observable continuous. ```scala // Convert to Signal val signal: Signal[Int] = numbers.startWith(0) // initial = 0 val weakSig: Signal[Option[Int]] = numbers.startWithNone ```