Skip to content
scalive
Menu
ConnectingLiveReconnectingOffline
GitHub

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.

LiveView and registration boundary
scala
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, Reset

Live result

ConnectedReconnectingRead-only

StatusWaiting for connected mount

HandleNot acquired

Model-only checks0

Disconnected. Controls resume after reconnection.

LiveView observability

Interaction inspector

See how actions in the live result above travel through typed messages, server state, protocol frames, and DOM updates.

Reconnect to inspect live interactions

The live result and its inspector resume when the connection returns.

Related guidance: acquire other connected resources.