Skip to main content

Observable Objects

Inject shared, reactive objects into views so UI updates when the object changes.

Observables Flow

How It Works

  • @observable([fields]) marks a view field as an observable reference; changes to the listed fields trigger invalidation.
  • @injectObservable('key', [fields]) injects an IoC-managed observable and subscribes to the listed fields.
  • Field filtering: pass a list of field names to observe only those. Defaults to ['__internalState'] (all changes).
  • Observable changes trigger makeDirty(field) → view invalidation → getViews() runs again.
  • Optionally override onObservableFieldUpdated(observableFieldName, subField) to intercept updates; return false to skip rerender.
Don’t Mutate During Render

Update observables from events or async handlers, not inside getViews().

Live Example

Try It

Increment/decrement updates the labels immediately. Toggling the name shows selective field observation.

Annotations

  • @observable(['field1','field2']) (on a view state field): marks that state field as an observable reference. The instance you assign should extend HsObservable. The optional array narrows which underlying observable fields trigger invalidation.
  • @injectObservable('serviceKey', ['field']) (on a view state field): resolves an HsObservable instance from IoC and observes only the listed fields. In examples we AppUtils.register('sharedObs', instance) inside getViews() to keep it simple.
  • Observable model properties: decorate properties inside your model class (which must extend HsObservable) with @observableField so changes notify observers.
    • API: static/api/functions/common.decorators.observableField.html.

BaseView Hooks

  • protected onObservableFieldUpdated(observableFieldName: string, field?: string): boolean — return false to prevent re-render for a specific update.
  • Observers are attached via updateObservable(fieldName, value) using filters registered by registerObservableField (done by the decorators).

Managing Observers Manually

In specialized cases, add/remove observers yourself. For example, the video player toggles an observer on shared player state:

private toggleVideoPlayerStateObserver(active: boolean): void {
if (this.videoPlayerState) {
this.videoPlayerState.removeObserver('__internalState', this);
}
if (active) {
this.videoPlayerState.addObserver('__internalState', this, (field) => {
switch (field) {
case 'control':
this.renderer.control = this.videoPlayerState.control;
break;
case 'seek':
this.renderer.seek = this.videoPlayerState.seek;
break;
}
});
}
}

ObservableObjects

Overview

ObservableObjects in Hosanna UI are special objects whose properties can be observed for changes. When an observable property changes, any UI component or logic bound to it will automatically update, enabling reactive and declarative UI patterns.

Creating an ObservableObject

To make an object observable, use the @observable() decorator on its properties or fields. Hosanna UI provides utilities to help you define and use observables easily.

Example:

import { observable } from 'hosanna-ui/lib/decorators';

export class SimpleObservable {
@observable() counter = 0;
@observable() name = 'John Doe';
}

Using ObservableObjects in Views

You can bind ObservableObjects to your views. When the observable's properties change, the UI will automatically reflect the new values.

Example:

import { view } from 'hosanna-ui/lib/decorators';
import { ObservableRigState } from './ObservableRig';

@view('ObservableRig')
export class ObservableRigView extends BaseExampleScreenView<ObservableRigState> {
@observable() myObservable = new SimpleObservable();

protected override getViews() {
return [
VGroup([
Label({ text: 'Counter: ' + this.myObservable.counter }),
Button({ text: 'Increment' }).onClick(() => this.myObservable.counter++),
Button({ text: 'Decrement' }).onClick(() => this.myObservable.counter--),
Button({ text: this.myObservable.name }).onClick(() => this.myObservable.name = ['John Doe', 'Jane Doe'][Math.floor(Math.random() * 2)]),
])
];
}
}

Passing Observables to Subviews

ObservableObjects can be passed as props to subviews, allowing for shared state and coordinated updates across components.

Example:

ObservableSubView({
myObservable: this.myObservable,
})

Injecting and Sharing Observables

You can register and inject observables using utilities like AppUtils.register to share state between different parts of your app.

Example:

AppUtils.register('myObservable', this.myObservable);
this.present(InjectObservableRig());

Best Practices

  • Use @observable() for any property that should trigger UI updates when changed.
  • Keep observable logic simple and focused on state that affects the UI.
  • Pass observables to subviews for shared, reactive state.
  • Use utilities like AppUtils for dependency injection and sharing observables.

See Also

  • hosanna-ui/src/hosanna-ui-examples/data/ObservableRig.ts
  • hosanna-ui/src/hosanna-ui-examples/data/InjectObservableRig.ts
  • hosanna-ui/src/hosanna-ui-examples/data/SimpleObservable.ts