Introduction
In the previous section, Connecting to a Browser Based UI, the backend was extended to expose configuration values and publish real-time thermostat state updates using the MessageBroker. With those pieces in place, the system is now ready to support a rich, browser-based user interface.
This section focuses on the frontend portion of the thermostat tutorial. It describes how the UI is structured, how it connects to the backend, and how backend data flows into React components using the KOS UI framework.
The goal of this section is not to build a visually complex interface, but to demonstrate the recommended patterns for modeling data, synchronizing state, and wiring user interactions in a maintainable and scalable way.
UI Project Structure
The user interface workspace is generated using the kosui command-line utility. This utility scaffolds a KOS-enabled React application and provides generators for common artifacts such as models and hooks.
A typical UI workspace contains:
- Models: Frontend data models that mirror backend state and configuration
- Hooks: React hooks for accessing models from components
- Views and Components: React components that render the UI
- Assets: Fonts, icons, images, and other static resources
Assets can be placed in an application’s assets folder and referenced throughout the UI. This keeps visual resources organized and decoupled from application logic.
In this example UI, Emotion styled components are used for styling. Styles are defined in separate .styles.tsx files to keep rendering logic focused and readable. This choice is not required, but it reflects a common pattern used by UI teams.
Generating Type-Safe API Services
The UI workspace also leverages a built-in Nx project target, api, to generate type-safe service definitions from the backend’s OpenAPI specification.
When executed, this target connects to a running KOS Studio–generated image (simulator or hardware), downloads the OpenAPI listing, and generates TypeScript types and service utilities that match the exposed APIs. This ensures the frontend stays aligned with the backend and provides compile-time validation for paths, parameters, and responses.
API services are generated using:
kosui api:generate --project your-projectThe target is configured in project.json with the device host and versioning information, allowing teams to consistently regenerate services as backend APIs evolve.
Although this tutorial primarily demonstrates declarative service requests using decorators (as shown in the thermostat model), the generated services enable an alternative imperative service-layer pattern when more control is required.
Re-run the api target whenever backend endpoints change or when switching between simulator and hardware to keep frontend types in sync.
The Thermostat UI Model
At the center of the UI is the Thermostat Model, which acts as the frontend representation of the thermostat’s state and configuration.
Rather than having React components talk directly to HTTP endpoints or WebSocket topics, the UI interacts with this model. The model encapsulates all communication with the backend and exposes a clean, reactive API to the rest of the UI.
Conceptually, the model mirrors the backend design described in Connecting to a Browser Based UI section:
- Configuration values (minimum and maximum temperature set points) are modeled as configs
- Thermostat state (current temperature and mode) is treated as transient, event-driven data
Binding Configuration Properties
The thermostat backend exposes minTemp and maxTemp as configuration values. On the frontend, these are bound using the @kosConfigProperty decorator:
@kosConfigProperty({
path: 'system:service:thermostat',
attribute: 'maxTemp',
})
maxTemp!: KosConfigProperty<number>;This decorator connects the frontend model to the backend configuration by path and attribute name. Once bound, the KOS framework keeps the value synchronized automatically. When the backend config changes, the UI model updates. When the UI updates the config, the change is sent back to the backend.
Because these values represent long-lived user intent, modeling them as configuration properties aligns with the backend’s design and avoids duplicating logic.
To make these values easy to consume in React components, the model exposes simple getters:
get maxTempValue() {
return this.maxTemp.value;
}Initializing Model State
In Part D, a REST endpoint was introduced to retrieve the current thermostat state. While real-time updates are delivered via events, the UI still needs an initial snapshot when it first loads.
This is handled using the @appServiceRequest decorator:
@kosServiceRequest({
path: '/api/system/thermostat/service/state',
lifecycle: DependencyLifecycle.LOAD,
})
getState(data: ThermostatOptions) {
this.updateModel(data);
}This decorator issues a service request automatically when the model is loaded. The response is passed into the method, allowing the model to initialize its internal state before the UI begins rendering.
This approach avoids manual data fetching in React components and ensures that the model is fully populated before it is used.
Handling Real-Time State Updates
As described in Part D, thermostat state updates are published to a MessageBroker topic whenever the temperature or mode changes.
On the frontend, the model subscribes to the same topic using the @kosTopicHandler decorator:
@kosTopicHandler({ topic: '/app/system/thermostat/state', websocket: true })
stateHandler(data: ThermostatOptions) {
this.updateModel(data);
}Each time a message is published on this topic, the handler is invoked and the model updates its internal state.
This event-driven approach eliminates the need for polling and ensures that the UI reacts immediately to backend changes, even if those changes originate outside the UI.
Updating Configuration from the UI
The model also exposes methods for updating configuration values:
increaseMaxTemp() {
const value = (this.maxTemp.rawValue || DEFAULT_TEMPERATURE) + 1;
this.maxTemp.updateProperty(value);
}These methods are invoked by UI components in response to user actions. Under the hood, calling updateProperty sends the new value to the backend configuration service.
Although configuration updates could also be implemented using service endpoints, this example intentionally demonstrates config property usage to reinforce the distinction between stable configuration and transient state.
Registering the Model
Before a model can be used in the UI, it must be registered with the KOS model registry:
KosModelRegistry.dispense.models().model(Thermostat);This registration step makes the model available throughout the application and enables features such as lifecycle management, dependency injection, and automatic synchronization.
The registration file also initializes the KOS React context provider:
const { KosCoreContextProvider } = initKosProvider();This provider must wrap the React application so that hooks and models function correctly.
Consuming the Model in React
React components access the thermostat model using a generated hook:
const { model: thermostat } = useThermostat();Once obtained, the component can read model properties and invoke model methods without needing to know how data is fetched or synchronized.
For example, the main view wires UI controls directly to model methods:
const increaseMaxTemp = useCallback(
() => thermostat?.increaseMaxTemp(),
[thermostat]
);And renders values from the model:
<TemperatureDisplay
mode={thermostat?.mode}
temperature={thermostat?.temperature}
/>Because the model updates reactively in response to configuration changes and topic events, the UI automatically re-renders when backend data changes.
Summary
In this section, the browser-based UI was connected to the thermostat backend using the KOS UI framework. The key ideas mirror those introduced in Part D:
- Configuration values are bound directly and kept in sync
- Transient state is delivered via events, not polling
- A frontend model encapsulates all backend communication
- React components remain simple and declarative
This architecture allows UI developers to build responsive interfaces without duplicating backend logic or managing low-level networking concerns.
At this point, the thermostat system is fully connected end-to-end: from backend services and message topics to a live, browser-based user interface.
