Skip to main content

Dependency Injection

Hosanna uses a simple IoC container and property decorators to inject services and shared state.

App Initialization and IoC

Initialization composes an IOCServiceMap and creates the container. Platform-specific overrides are applied, then custom services. Initialization order matters: base → platform → custom.

export class PlatformAppInitializer extends BaseAppInitializer {
protected override getPlatformSpecificServices(): IOCServiceMap {
return [
{ key: 'systemService', clazz: RokuSystemService },
{ key: 'deviceRegistry', clazz: DeviceRegistry },
{ key: 'hosannaDevice', clazz: RokuDevice },
{ key: 'measurementUtils', clazz: MeasurementUtils },
];
}
}
export class BaseAppInitializer {
protected getDefaultServices(): IOCServiceMap {
return [
{ key: 'developerUtils', clazz: DeveloperUtils },
{ key: 'systemService', clazz: RokuSystemService },
{ key: 'timerService', clazz: TimerService },
{ key: 'instancePool', clazz: InstancePool },
{ key: 'viewManager', clazz: ViewManager },
{ key: 'viewBuilder', clazz: ViewBuilder },
{ key: 'hosannaDevice', clazz: RokuDevice },
{ key: 'remoteDebugClient', clazz: RemoteDebugClient },
{ key: 'textToSpeechManager', clazz: TextToSpeechManager },
{ key: 'focusManager', clazz: FocusManager },
{ key: 'styleRegistry', clazz: StyleRegistry },
{ key: 'fontManager', clazz: FontManager },
{ key: 'viewFragmentProvider', clazz: ViewFragmentProvider },
{ key: 'nodePool', clazz: NodePool },
{ key: 'notificationCenter', clazz: NotificationCenter },
{ key: 'asyncCommandHandler', clazz: AsyncCommandHandler },
{ key: 'thumbnailManager', clazz: ThumbnailManager },
{ key: 'sharedRemoteDebugState', clazz: SharedRemoteDebugState },
{ key: 'sharedDeeplinkInfo', clazz: SharedDeeplinkInfo },
];
}
createIOCContainer(custom: IOCServiceMap = []): IoCContainer {
const merged = this.mergeServices(
this.mergeServices(this.getDefaultServices(), this.getPlatformSpecificServices()),
custom,
);
return new IoCContainer(merged);
}
}

Import the platform initializer via @hs-platform/PlatformAppInitializer to get the right implementation for the current build target.

IoC and Injection

Injecting Services

  • @inject() with no args resolves lazily using the property name as the key, via IoC. Example: @inject() viewManager resolves key viewManager.
  • @inject('serviceKey') resolves lazily from IoC using the provided key.
  • @injectState('serviceKey') resolves once and stores in view state.
  • @injectObservable('serviceKey', [fields]) injects an observable object and auto-wires re-render updates for listed fields.

Resolution behavior

  • If a key exists in the container, that instance is returned.
  • If not, the container attempts to instantiate using the registered class (from the service map), caches it, and returns it.
Pick the Right Decorator

Use @inject for stateless services, @injectState when you want the value persisted in view state, and @injectObservable for shared, reactive objects.

Live Example

Try It

Open the example and see how dependencies are available without manual wiring.

Injection

Overview

Injection in Hosanna UI allows you to share and reuse stateful objects (such as ObservableObjects) between different parts of your application. This is useful for managing global state, sharing data between screens, or implementing service-like patterns.

Resolving and Registering Values

You can resolve, register, and retrieve any value from the IoC container. Convenience methods exist on AppUtils with generics.

Example:

import { AppUtils } from 'hosanna-bridge-lib/AppUtils';
import { IoCContainer } from 'hosanna-bridge-lib/IocContainer';

// Resolve a service (generic typed)
const vm = AppUtils.resolve<IViewManager>('viewManager');

// Register a concrete instance
AppUtils.register('appInfo', { version: '1.0.0' });

// Later, in another view or component
const appInfo = AppUtils.get<{ version: string }>('appInfo');

// Under the hood (direct container API)
IoCContainer.resolve('viewManager');

Use Case: Sharing State Between Screens

You can inject an observable into another screen or subview, allowing both to react to changes in shared state.

Example:

// In the first screen
AppUtils.register('myObservable', this.myObservable);
this.present(InjectObservableRig());

// In InjectObservableRig
const myObservable = AppUtils.get('myObservable');
// Now you can use myObservable and bind it to UI

Best Practices

  • Initialize base, then platform overrides, then custom services; keep keys stable.
  • Use unique keys when registering objects to avoid collisions.
  • Always unregister or clean up objects if they are no longer needed to prevent memory leaks.
  • Prefer using injection for shared state or services, not for tightly-coupled dependencies.
  • Combine injection with observables for powerful, reactive state management across your app.

See Also

  • Example: hosanna-ui/src/hosanna-ui-examples/data/ObservableRig.ts
  • Example: hosanna-ui/src/hosanna-ui-examples/data/InjectObservableRig.ts
  • Example: hosanna-ui/src/hosanna-bridge-lib/AppUtils.ts

Injection is a key pattern for scalable, maintainable applications in Hosanna UI, enabling loose coupling and shared, reactive state.