Skip to content

9.6. NiceGUI Integration

NiceGUI is a Python web framework that exposes declarative UI components. Wire a ComponentVMOf[M] by subscribing to the VMx hub and calling element.update() (or rebinding .text, .value, etc.) on property changes.

9.6.1. Reactivity primitive

NiceGUI elements expose mutable .text, .value, .props, and similar attributes. Pushing a new value followed by element.update() re-renders the element. There is no built-in observable model.

9.6.2. Mapping

NiceGUI VMx
ui.label('x').bind_text_from(...) subscribe + assign in handler
ui.button(on_click=fn) command.execute(None) inside on_click
ui.refreshable-decorated builder re-build when CollectionChangedMessage fires
app.add_timer / asyncio loop RxDispatcher.asyncio(loop)

9.6.3. Adapter skeleton

from collections.abc import Callable

from nicegui import ui
import reactivex.operators as ops
from vmx import ComponentVMOf, Message, MessageHubProto, PropertyChangedMessage, RelayCommand

def bind_label(label: ui.label, vm: ComponentVMOf[Note],
               hub: MessageHubProto[Message]) -> Callable[[], None]:
    label.text = vm.model.title

    def _on_msg(msg: PropertyChangedMessage[object]) -> None:
        if msg.property_name == "model":
            label.text = vm.model.title
            label.update()

    sub = hub.messages.pipe(
        ops.filter(lambda m: isinstance(m, PropertyChangedMessage) and m.sender is vm),
    ).subscribe(_on_msg)
    return sub.dispose  # call on page teardown

# Page builder:
def render(vm: ComponentVMOf[Note], hub: MessageHubProto[Message],
           save_command: RelayCommand) -> None:
    label = ui.label()
    dispose = bind_label(label, vm, hub)
    ui.button("Save", on_click=lambda: save_command.execute(None))
    ui.context.client.on_disconnect(dispose)

9.6.4. Fuller example

No worked NiceGUI Notes-Showcase ships yet. The Textual recipe (textual.md) uses the same hub-subscription shape and is a good reference.