name: Create Entity ViewModel description: Create a new entity ViewModel with property ViewModels following {{sharedLib}} MVVM patterns. Use when adding new data models, creating entity wrappers, or scaffolding ViewModels for entities. Handles property VM creation, label converters, base class selection, and test generation. allowed-tools: Read, Write, Edit, Grep, Glob
Create Entity ViewModel
This skill scaffolds a complete entity ViewModel following {{sharedLib}} framework patterns.
When to Use
- Creating a new entity ViewModel for a data model
- Wrapping an existing model with UI logic
- Adding property ViewModels for entity properties
- Setting up label converters for enum types
Prerequisites
- Data model must exist in
/model/*.model/src/and be generated - Run
npm run generate-modelif model was just created - Entity should be defined with proper property types (ValueProperty, CommandedProperty, etc.)
Process
Step 1: Understand the Data Model
Read the source model to understand:
- Property types (ValueProperty, CommandedProperty, RangedCommandedProperty)
- Enum types that need label converters
- Geographic properties (lat/lon/alt)
- Parent class (Entity, GeoEntity, Platform, etc.)
Reference: ENTITY_ARCHITECTURE.md
Step 2: Choose the Right Base Class
Determine appropriate base class:
- EntityViewModel - Standard entities (settings, configs)
- GeoEntityBaseVM - Entities with location data (platforms, vehicles)
- GeoPointBaseVM - Simple geographic points (waypoints, markers)
- Custom abstract base - Multiple related entities share functionality
Reference: ENTITY_ARCHITECTURE.md - Best Practices
Step 3: Create Property ViewModels
For each property on the model, create appropriate property VM:
Property Type Mapping:
ValueProperty<string>→StringViewModelCommandedProperty<string>→CommandedStringViewModelValueProperty<number>→NumberViewModelRangedCommandedProperty<number>→RangedCommandedNumberViewModelCommandedProperty<boolean>→CommandedBooleanViewModelCommandedProperty<EnumType>→CommandedEnumViewModel<EnumType>ValueProperty<string[]>→ArrayViewModel<string>CommandedProperty<string[]>→CommandedArrayViewModel<string>
CRITICAL Patterns:
- Use
CommandedArrayViewModel, NOTArrayViewModelfor commanded arrays - Use
RangedCommandedNumberViewModelfor numbers with min/max, NOTCommandedNumberViewModel - Always add type hints for labelConverter:
(value: number | null | undefined) => string - Return
'---'for null/undefined values in labelConverter
@computed Decorator Usage:
Use @computed when getter:
- Includes configuration (labelConverter, defaultValue, etc.) - prevents re-running configure
- Derives/computes values from observables
- Filters or transforms data
// ✅ With configuration - use @computed
@computed
get modeVM(): ICommandedVM<ModeType, IEnumFormatOptions<ModeType>> {
const vm = this.createPropertyVM('mode', CommandedEnumViewModel<ModeType>);
vm.configure({
labelConverter: ModeTypeLabel,
defaultValue: ModeType.DEFAULT
});
return vm;
}
// ✅ Simple forwarding - @computed optional (micro-optimization)
get nameVM(): IPropertyVM<string, IStringFormatOptions> {
return this.createPropertyVM('name', CommandedStringViewModel);
}
// ✅ Derived values - @computed required
@computed
get activeItems(): EntityViewModel[] {
return this.items.filter(item => item.isActive);
}
Reference: ENTITY_ARCHITECTURE.md - Property ViewModel Patterns
Step 4: Create Label Converters for Enums
For each enum type, create a label converter in adapters/types.ts:
export const YourEnumTypeLabel: Record<YourEnumType, string> = {
[YourEnumType.VALUE1]: 'Display Label 1',
[YourEnumType.VALUE2]: 'Display Label 2',
};
Reference: ENTITY_ARCHITECTURE.md - Label Converter Creation
Step 5: Implement Required Methods
All entity ViewModels must implement:
getEntityClassName()- ReturnsModelClass.classgetEntityCtr()- Returns the model constructor
Reference: COOKBOOK_PATTERNS_ENHANCED.md - Complete Entity ViewModel
Standard Import Pattern:
// Framework interfaces and types
import { IEntityConstructor, IFrameworkServices, IPropertyVM, ICommandedVM, IEnumFormatOptions, IStringFormatOptions, INumberFormatOptions } from '@{{company}}/framework-api';
// Entity ViewModel base and property VMs (from {{sharedLib}}-core)
import { EntityViewModel, CommandedStringViewModel, CommandedEnumViewModel, RangedCommandedNumberViewModel, CommandedArrayViewModel } from '@{{company}}/{{sharedLib}}-core';
// MobX decorators
import { computed, makeObservable } from 'mobx';
// Model and types
import { MyEntity } from '@{{company}}/your-model-package';
import { MyEnumType } from '@{{company}}/your-model-package';
// Label converters (from adapters)
import { MyEnumTypeLabel } from '../adapters/types';
Step 6: Add MobX Support
CRITICAL: Call makeObservable(this) in constructor!
constructor(services: IFrameworkServices) {
super(services);
makeObservable(this); // REQUIRED for reactivity
}
Reference: MOBX_ESSENTIALS.md - Constructor Pattern
Step 7: Update Barrel Exports
Add to {lib}.core/src/index.ts:
export * from './lib/viewModels/yourEntityViewModel';
Add label converters to {lib}.core/src/lib/adapters/index.ts:
export * from './types';
Step 8: Create Tests
Generate unit tests following patterns:
- Mock IFrameworkServices
- Test property VM creation
- Test getEntityClassName/getEntityCtr
- Test label converters
Reference: TESTING_GUIDE.md
Common Pitfalls to Avoid
Reference: COMMON_PITFALLS.md
- ❌ Don't use
BaseEntityViewModel- UseEntityViewModelfrom {{sharedLib}}-core - ❌ Don't use
ArrayViewModelfor commanded arrays - UseCommandedArrayViewModel - ❌ Don't forget
makeObservable(this)in constructor - ❌ Don't use
CommandedNumberViewModelfor constrained numbers - UseRangedCommandedNumberViewModel - ❌ Don't hardcode labels - Create label converters in adapters
- ❌ Don't forget type hints on labelConverter functions
- ❌ Don't return 'N/A' for null - Use
'---' - ❌ Don't forget
@computedon property VMs with configuration or derived values
Complete Template
Reference: COOKBOOK_PATTERNS_ENHANCED.md - Complete Entity ViewModel
File Locations
- Entity ViewModels:
{lib}.core/src/lib/viewModels/ - Label Converters:
{lib}.core/src/lib/adapters/types.ts - Tests:
{lib}.core/src/lib/viewModels/__tests__/
Verification Steps
After creation:
- Run
./tools/build-helpers/count-client-errors.sh- Should be 0 - Run
npm test- New tests should pass - Verify exports in index.ts
- Check label converters work in UI components
Ask User If Unclear
- Which library to create the VM in ({{projectName}}.core, alpha.core, etc.)
- Whether this is a geographic entity (needs GeoEntityBaseVM)
- Default values for enum properties
- Whether to create abstract base class (if multiple similar entities)