What You’ll Build
Extend your color manager to depend on the counter model, adding a computed property that returns different colors based on whether the counter is even or odd. This demonstrates how models can observe each other and how changes in one model automatically trigger updates in dependent models and their UIs—without any manual synchronization.
Prerequisites
- Completed tutorials 02 (counter), 03 (service requests), and 04 (device events)
- Counter model auto-incrementing every 2 seconds
- Color manager model changing colors every 3 seconds
- Development server running
Understanding Model Dependencies
Models often need to access data or functionality from other models. The @kosDependency decorator enables dependency injection—the framework automatically provides references to other models when your model needs them.
Key benefits:
- Automatic injection – Framework resolves and injects dependencies
- Type safety – Full TypeScript support for dependency types
- Cross-model observation – Read observable properties from other models
- Transitive reactivity – Changes in dependency → dependent model → UI (automatically)
Step 1: Add Dependency Decorator Import
Open libs/my-first-kos-app-models/src/models/color-manager/color-manager-model.ts.
Add kosDependency to your imports:
import {
kosModel,
IKosDataModel,
kosLoggerAware,
KosLoggerAware,
kosServiceRequest,
kosTopicHandler,
EventBus,
DependencyLifecycle,
kosDependency, // Add this
} from "@kosdev-code/kos-ui-sdk";[Screenshot: VS Code showing the import statement with kosDependency highlighted]
Step 2: Import Counter Type
Add the Counter model type import at the top of the file:
import type { ColorManagerOptions } from "./types";
import type { CounterModel } from "../counter"; // Add this lineWhy: TypeScript needs the CounterModel type to provide IntelliSense and type checking for the dependency property.
Step 3: Add Counter Dependency Property
Add a property decorated with @kosDependency to declare the dependency:
@kosModel({ modelTypeId: MODEL_TYPE, singleton: true })
@kosLoggerAware()
export class ColorManagerModelImpl implements IKosDataModel {
currentColor: string = '#000000';
// Dependency property - decorated with @kosDependency
@kosDependency({ modelType: 'counter-model' })
counter!: CounterModel;
// ... rest of implementation
}[Screenshot: VS Code showing the counter property with @kosDependency decorator]
What this does:
- The
@kosDependencydecorator tells the framework to inject the counter model into this property - The
!(definite assignment) indicates the property will be set by the framework beforeinit()runs - TypeScript will provide full IntelliSense for
this.counter
Step 4: Add Computed Getter Based on Counter
Add a computed property that reads the counter’s state:
@kosModel({ modelTypeId: MODEL_TYPE, singleton: true })
@kosLoggerAware()
export class ColorManagerModelImpl implements IKosDataModel {
currentColor: string = '#000000';
@kosDependency({ modelType: 'counter-model' })
counter!: CounterModel;
// Computed property that observes the counter model
get accentColor(): string {
// Read counter.count - this creates an observation
const isEven = this.counter.count % 2 === 0;
return isEven ? '#00FF00' : '#FF0000'; // Green for even, red for odd
}
// ... rest of implementation
}[Screenshot: VS Code showing the getter method]
Why a getter? Getters automatically recalculate when their dependencies change. When this.counter.count changes, any component or code reading accentColor will automatically get the new value.
No manual updates needed: The reactive system tracks that accentColor depends on counter.count and automatically triggers re-renders when the counter changes.
Step 5: Update UI Component to Show Both Colors
Open apps/my-first-kos-app-ui/src/app/components/color-display.tsx and update it:
import { kosComponent } from '@kosdev-code/kos-ui-sdk';
import { useKosModel } from '@kosdev-code/kos-ui-sdk';
export const ColorDisplay = kosComponent(() => {
const { model: colorManager, ready } = useKosModel({
modelType: 'color-manager-model'
});
if (!ready) {
return <div>Loading colors...</div>;
}
const evenOdd = colorManager.counter.count % 2 === 0 ? 'Even' : 'Odd';
return (
<div style={{ padding: '20px', fontFamily: 'Arial' }}>
<h2>Color Display with Dependencies</h2>
<div style={{ display: 'flex', gap: '20px', marginBottom: '20px' }}>
<div>
<div style={{
width: '150px',
height: '150px',
backgroundColor: colorManager.currentColor,
border: '2px solid #333',
borderRadius: '8px'
}} />
<p style={{ fontWeight: 'bold', marginTop: '10px' }}>Main Color</p>
<p style={{ fontSize: '14px', color: '#666' }}>From events (every 3s)</p>
<p style={{ fontSize: '12px', fontFamily: 'monospace' }}>
{colorManager.currentColor}
</p>
</div>
<div>
<div style={{
width: '150px',
height: '150px',
backgroundColor: colorManager.accentColor,
border: '2px solid #333',
borderRadius: '8px'
}} />
<p style={{ fontWeight: 'bold', marginTop: '10px' }}>Accent Color</p>
<p style={{ fontSize: '14px', color: '#666' }}>From counter (every 2s)</p>
<p style={{ fontSize: '12px', fontFamily: 'monospace' }}>
{colorManager.accentColor}
</p>
<p style={{ fontSize: '14px', marginTop: '5px' }}>
Counter: {colorManager.counter.count} ({evenOdd})
</p>
</div>
</div>
<div style={{ fontSize: '12px', color: '#999' }}>
<p>Main color cycles through array (independent)</p>
<p>Accent color toggles between green (even) and red (odd) based on counter</p>
</div>
</div>
);
});[Screenshot: VS Code showing the complete updated ColorDisplay component]
What’s happening in the component:
- Component reads
colorManager.accentColorgetter - Getter reads
this.counter.count(creates observation) - When counter increments,
countchanges - Reactive system detects change, re-evaluates getter
- Component automatically re-renders with new color
- Zero manual synchronization code
Step 6: See It Work
Save all files. The browser automatically refreshes.
What happens:
You’ll now see three independent update cycles working together:
- Counter incrementing – Every 2 seconds (from tutorial 02)
- Main color changing – Every 3 seconds (from tutorial 04)
- Accent color toggling – Every 2 seconds, automatically synchronized with counter even/odd state
[Screenshot: Browser showing the counter at the top, and color display with two colored boxes side by side – one cycling through colors, the other toggling between green and red]
Observe the behavior:
- The counter increments: 1, 2, 3, 4, …
- When counter is even (2, 4, 6, …) → accent box is green
- When counter is odd (1, 3, 5, …) → accent box is red
- The main color box continues to cycle independently through the color array
- All updates happen automatically—no manual synchronization
Open the Console (F12) and you’ll see:
Counter model initialized
Color manager initialized
Counter model loaded
Color manager loaded
Counter incremented to 1
Counter incremented to 2
Color change event received: { color: "#33FF57", timestamp: 1234567890 }
Counter incremented to 3
...[Screenshot: Chrome DevTools Console showing interleaved logs from counter and color manager]
Notice: There are no logs for “accent color updated” because we’re not manually updating it. The getter automatically recalculates when accessed by the component after the counter changes. This is pure reactive programming—the UI just reads the value, and the framework handles change detection.
What Just Happened
You built a multi-model reactive system:
- Dependency Declaration –
@kosDependencyproperty decorator declares dependencies - Automatic Injection – Framework injects counter reference before
init()runs - Cross-Model Observation – Color manager getter reads
counter.countobservable property - Computed Properties –
accentColorgetter observes counter and recalculates automatically - Transitive Reactivity – Counter change → Getter recalculation → UI re-render
- Zero Manual Sync – No setInterval, no manual updates, no event listeners
The key insight: Models can observe other models just like components observe models. The reactive system automatically tracks dependencies and propagates changes through the entire chain—you just write getters that read properties, and everything else happens automatically.
Key Concepts Demonstrated
Dependency Injection: The @kosDependency property decorator declares dependencies. The framework resolves and injects them before init() runs, ensuring dependencies are always available when you need them.
Cross-Model Observation: When color manager’s accentColor getter reads this.counter.count, it creates an observation. Any change to counter.count automatically triggers recalculation when something reads accentColor.
Transitive Reactivity: Changes flow automatically through the dependency chain:
- Counter model increments
countproperty - Color manager’s
accentColorgetter depends oncounter.count - UI component reads
accentColor - When counter changes, component re-renders with new getter value
- No manual synchronization needed at any step
Computed Cross-Model Properties: Getters can depend on properties from other models. The reactive system tracks these dependencies across model boundaries and ensures getters recalculate when any dependency changes.
Lifecycle Coordination: The KOS framework ensures dependencies are initialized before dependent models. You never have to worry about initialization order—the framework handles it based on the dependency graph.
Understanding Dependency Lifecycle
The @kosDependency decorator accepts a lifecycle parameter:
@kosDependency({
modelType: 'counter-model',
lifecycle: DependencyLifecycle.INIT // Default
})
counter!: CounterModel;Available lifecycle phases:
DependencyLifecycle.INIT(default) – Inject beforeinit()runsDependencyLifecycle.LOAD– Inject beforeload()runsDependencyLifecycle.READY– Inject after model is fully ready
For most use cases, the default INIT is correct—you want dependencies available as early as possible.
Multiple Dependencies
Models can depend on multiple other models:
@kosModel({ modelTypeId: MODEL_TYPE, singleton: true })
@kosLoggerAware()
export class DashboardModelImpl implements IKosDataModel {
@kosDependency({ modelType: 'counter-model' })
counter!: CounterModel;
@kosDependency({ modelType: 'settings-model' })
settings!: SettingsModel;
@kosDependency({ modelType: 'user-model' })
user!: UserModel;
get displayName(): string {
// Can observe multiple dependencies
return `${this.user.name} - Count: ${this.counter.count}`;
}
get theme(): string {
// Getters can read from different dependencies
return this.settings.darkMode ? 'dark' : 'light';
}
}Each property can have its own @kosDependency decorator. The framework injects all of them before init() runs.
Try This
Enhance the cross-model reactivity:
- Add a getter
get countDisplay(): stringto color manager that formats the counter:"Count: ${this.counter.count}" - Add another getter
get counterStatus(): stringthat returns “High”, “Medium”, or “Low” based on count ranges - Display these computed values in the UI component
- Watch how the component automatically updates when the counter changes—without any additional code
You’ll see that adding computed properties across multiple models requires no changes to component logic—just add getters that read from dependencies, and components automatically update.
Real-World Use Cases
Model dependencies are used throughout KOS applications:
Device Models Depending on Assembly Models:
@kosDependency({ modelType: Assembly.type })
assemblyModel!: AssemblyModel;
get waterPump(): PumpModel | undefined {
return this.assemblyModel.getHoldersByGroup('water')[0]?.pumps[0];
}UI Models Depending on Device Models:
@kosDependency({ modelType: 'device-status-model' })
deviceStatus!: DeviceStatusModel;
get canPour(): boolean {
return this.deviceStatus.isReady && !this.deviceStatus.hasTroubles;
}Coordinator Models Depending on Multiple Service Models:
@kosDependency({ modelType: 'network-model' })
networkModel!: NetworkModel;
@kosDependency({ modelType: 'ota-model' })
otaModel!: OtaModel;
@kosDependency({ modelType: 'storage-model' })
storageModel!: StorageModel;
get systemHealth(): string {
if (!this.networkModel.isConnected) return 'offline';
if (this.otaModel.isUpdating) return 'updating';
if (this.storageModel.isLow) return 'low-storage';
return 'healthy';
}Next Steps
Now that you understand model dependencies, you can:
- Build coordinator models – Models that orchestrate multiple other models
- Create computed dashboards – Models that aggregate data from dependencies
- Implement cross-cutting concerns – Models that observe multiple domains
- Design model architectures – Structure applications with clear dependency graphs
The dependency injection system enables clean separation of concerns while maintaining type safety and automatic reactivity.
Next: Learn how to manage collections of models with containers.
