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.
Injecting Services
@inject()
with no args resolves lazily using the property name as the key, via IoC. Example:@inject() viewManager
resolves keyviewManager
.@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.
Use @inject
for stateless services, @injectState
when you want the value persisted in view state, and @injectObservable
for shared, reactive objects.
Live Example
Open the example and see how dependencies are available without manual wiring.
Injection
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.