| name | Create Entity ViewModel |
| description | Create a new entity ViewModel with property ViewModels following Phoenix 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 Phoenix 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 '@tektonux/framework-api';
// Entity ViewModel base and property VMs (from phoenix-core)
import { EntityViewModel, CommandedStringViewModel, CommandedEnumViewModel, RangedCommandedNumberViewModel, CommandedArrayViewModel } from '@tektonux/phoenix-core';
// MobX decorators
import { computed, makeObservable } from 'mobx';
// Model and types
import { MyEntity } from '@tektonux/your-model-package';
import { MyEnumType } from '@tektonux/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 phoenix-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 (faad.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)