Connected lifecycle registration
The connected mount acquires a concrete registration and stores its handle in the initial model. Updating or resetting ordinary model state does not reacquire the registration. The preview makes acquisition visible; after its LiveView is removed there is no remaining UI in which to show cleanup. The extracted source and its lifecycle tests verify that closing the LiveView releases the exact displayed handle.
Open the page in another tab to see a distinct handle for that independent lifecycle. Nested LiveViews follow the same ownership rule even when they share one WebSocket with their parent. State that should be shared by a logical application session belongs in a longer-lived service instead.
final case class LifecycleRegistration(id: String)
trait LifecycleRegistrations:
def register(owner: String): UIO[LifecycleRegistration]
def unregister(registration: LifecycleRegistration): UIO[Unit]
final class ConnectedResourceExample(
instanceId: String,
registrations: LifecycleRegistrations)
extends LiveView[ConnectedResourceExample.Msg, ConnectedResourceExample.Model]:
import ConnectedResourceExample.*
def mount(ctx: MountContext): Task[Model] = ctx.connection match
case Connection.Disconnected => ZIO.succeed(Model())
case Connection.Connected(connected) =>
connected.resources
.acquireRelease(registrations.register(instanceId))(registrations.unregister)
.map(registration => Model(Some(registration)))
def handleMessage(model: Model, ctx: MessageContext) =
case Msg.Check =>
ZIO.succeed(model.copy(checks = model.checks + 1))
case Msg.Reset =>
ZIO.succeed(model.copy(checks = 0))
def view(model: Signal[Model]): HtmlElement[Msg] =
val status = model.map(_.registration.fold("Waiting for connected mount")(_ => "Acquired"))
val handle = model.map(_.registration.fold("Not acquired")(_.id))
div(
cls := "docs-managed-work",
sectionTag(
cls := "docs-managed-work-state",
aria.label := "Connected lifecycle registration",
p("Status", strong(dataAttr("connected-resource-status") := "", status)),
p("Handle", code(dataAttr("connected-resource-handle") := "", handle)),
p(
"Model-only checks",
strong(dataAttr("connected-resource-checks") := "", model.map(_.checks.toString))
)
),
div(
cls := "docs-managed-work-controls",
button(typ := "button", on.click(Msg.Check), "Update model"),
button(typ := "button", on.click(Msg.Reset), "Reset checks")
)
)
end ConnectedResourceExample
object ConnectedResourceExample:
final case class Model(
registration: Option[LifecycleRegistration] = None,
checks: Int = 0)
enum Msg:
case Check, ResetView source (documentation/site/src/scalive/docs/examples/ConnectedResourceExample.scala:10-65)
Live result
ConnectedReconnectingRead-onlyStatusWaiting for connected mount
HandleNot acquired
Model-only checks0
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: acquire other connected resources.