Skip to main content

Dependency Injection

Hosanna creates one application IoC container during launch. Framework services are registered first, platform services can replace them, and your custom services are applied last. Resolving a registered class creates and caches its instance.

Register application services

Base, platform, and application service recipes merge into an IoC container that resolves and caches instances for injection decoratorsBase, platform, and application service recipes merge into an IoC container that resolves and caches instances for injection decorators

Override getCustomIOCServices() in your BaseApp subclass:

import { IOCServiceMap } from '@hs-src/hosanna-bridge-core/IocContainer';

export class App extends BaseApp {
protected override getCustomIOCServices(): IOCServiceMap {
return [
{ key: 'catalogueService', clazz: CatalogueService },
{ key: 'session', clazz: Session },
];
}
}

A custom entry with the same key replaces the base or platform mapping. Keep keys stable and register dependencies before a view first accesses them.

For an instance created at runtime, use AppUtils.register():

const session = new Session();
AppUtils.register('session', session);

const resolved = AppUtils.resolve<Session>('session');

AppUtils.resolve<T>(key, isRequired = true) is the public lookup helper. Passing false makes a missing service optional. There is no AppUtils.get() or public static IoCContainer.resolve() API.

Choose an injection decorator

@inject

Use @inject() for ordinary services. With no argument, the property name is the service key:

export class DetailsScreenView extends BaseView<DetailsScreenState> {
@inject() private catalogueService!: CatalogueService;

@inject('session')
private currentSession!: Session;
}

On TypeScript targets the property resolves lazily on first access and caches the result. Generated platform code may resolve at a different lifecycle point, so startup services should still be registered before views are instantiated.

@injectState

Use @injectState() on a BaseView when the injected value must be stored through the view's state machinery:

@injectState('session')
session!: Session;

@injectObservable

Use @injectObservable() for a shared HsObservable. It resolves the object, stores it in view state, and subscribes the view to the requested fields:

@injectObservable('playbackState', ['position', 'state'])
playbackState!: PlaybackState;

Omitting the field list uses ['__internalState'], which receives changes from all of the observable object's decorated fields.

Service or state?

Use @inject for services, @injectState for a value managed as view state, and @injectObservable when changes inside a shared observable should invalidate the view.

Device layout is an injected service

The platform registers one deviceLayout object. It is updated in place when screen metrics change:

import type { IHosannaDeviceLayoutInfo } from '@hs-src/hosanna-bridge-core/api';

export class DetailsScreenView extends BaseView<DetailsScreenState> {
@inject()
protected deviceLayout!: IHosannaDeviceLayoutInfo;

protected override getViews() {
return [
Group([
// Content can use contentX/contentY and contentWidth/contentHeight.
]),
];
}
}

See the safe-area guide for the available content and inset measurements.

Runtime registration of shared state

The samples register an existing observable before presenting a view that injects it:

AppUtils.register('myObservable', this.myObservable);
this.present(InjectObservableRig());
export class InjectObservableRigView extends BaseView {
@injectObservable()
myObservable!: SimpleObservable;
}

Registration replaces the value stored under that key. The public helper does not provide an unregister operation; choose application-lifetime keys or manage shorter-lived values explicitly.

Talk to us