Managed clock subscription
Start the clock at one tick per second, then replace it with a faster stream without managing a fiber yourself. Cancel stops delivery while retaining the visible tick history. Reset both cancels the stream and restores the initial model.
Each embedded instance derives its SubscriptionKeyopaque type SubscriptionKey = StringA nominal runtime key for one managed LiveView or component subscription.
from its documentation instance ID. The key names one resource inside its
owning LiveView; it is not application state or a global subscription name.
final class SubscriptionClockExample(instanceId: String)
extends LiveView[SubscriptionClockExample.Msg, SubscriptionClockExample.Model]:
import SubscriptionClockExample.*
private val ClockSubscription = subscriptionKey(instanceId)
def mount(ctx: MountContext): Task[Model] =
ZIO.succeed(Model())
def handleMessage(model: Model, ctx: MessageContext) =
case Msg.Start =>
if model.mode == Mode.Stopped then
ctx.subscriptions
.start(ClockSubscription, SubscriptionDelivery.Lossless)(ticks(1.second))
.as(model.copy(mode = Mode.EverySecond))
else ZIO.succeed(model)
case Msg.Replace =>
ctx.subscriptions
.replace(ClockSubscription, SubscriptionDelivery.Lossless)(ticks(250.millis))
.as(model.copy(mode = Mode.FourTimesPerSecond))
case Msg.Cancel =>
ctx.subscriptions.cancel(ClockSubscription).as(model.copy(mode = Mode.Stopped))
case Msg.Reset =>
ctx.subscriptions.cancel(ClockSubscription).as(Model())
case Msg.Tick(at) =>
ZIO.succeed(model.copy(lastTick = Some(at), tickCount = model.tickCount + 1))
def view(model: Signal[Model]): HtmlElement[Msg] =
div(
cls := "docs-managed-work",
sectionTag(
cls := "docs-managed-work-state",
aria.label := "Clock subscription state",
p("Mode", strong(dataAttr("clock-mode") := "", model.map(_.mode.label))),
p("Ticks received", strong(dataAttr("clock-count") := "", model.map(_.tickCount.toString))),
p(
"Latest tick",
span(dataAttr("clock-tick") := "", model.map(_.lastTick.fold("Waiting")(_.toString)))
)
),
div(
cls := "docs-managed-work-controls",
button(
typ := "button",
disabled := model.map(_.mode != Mode.Stopped),
on.click(Msg.Start),
"Start every second"
),
button(typ := "button", on.click(Msg.Replace), "Replace with fast clock"),
button(
typ := "button",
disabled := model.map(_.mode == Mode.Stopped),
on.click(Msg.Cancel),
"Cancel clock"
)
)
)
end SubscriptionClockExample
object SubscriptionClockExample:
enum Mode(val label: String):
case Stopped extends Mode("Stopped")
case EverySecond extends Mode("Every second")
case FourTimesPerSecond extends Mode("Four times per second")
final case class Model(
mode: Mode = Mode.Stopped,
lastTick: Option[Instant] = None,
tickCount: Int = 0)
enum Msg:
case Start
case Replace
case Cancel
case Reset
case Tick(at: Instant)
private[docs] def subscriptionKey(instanceId: String): SubscriptionKey =
SubscriptionKey(s"subscription-clock-$instanceId")
private def ticks(every: Duration): ZStream[Any, Nothing, Msg] =
ZStream.tick(every).mapZIO(_ => Clock.instant).map(Msg.Tick(_))View source (documentation/site/src/scalive/docs/examples/SubscriptionClockExample.scala:11-92)
Live result
ConnectedReconnectingRead-onlyModeStopped
Ticks received0
Latest tickWaiting
Disconnected. Controls resume after reconnection.
Interaction inspector
See how actions in the live result above travel through typed messages, server state, protocol frames, and DOM updates.
The live result and its inspector resume when the connection returns.
Related guidance: own long-lived streams with subscriptions.