Ux Menu
Ecosistema completo para organización y gestión de contenido con interfaces espaciales
UX Menu Component
The UX Menu is a flexible, customizable navigation menu component that supports multiple levels of nested items, search functionality, and persistent configuration. It can be used anywhere in an application where hierarchical navigation is needed.
Core Features
- Multi-level navigation with expandable/collapsible sections
- Search functionality for filtering menu items
- Badge support for showing notifications or counters
- Theme support with light and dark variants
- Persistent configuration saved to IndexedDB
- Customizable styling with icons and visual indicators
Architecture
The UX Menu system consists of several interconnected components and services:
Components
UxMenuComponent- The main menu UI componentUxMenuItemComponent- Individual menu item component (used internally)UxMenuDemoComponent- A demo component for testing and configuring menus
Services
UxMenuStorageService- Handles saving and loading menu configurationsUxMenuService- Provides menu registration and management functionality
Models
UxMenuConfig- Configuration interface for the menuUxMenuItem- Interface for menu items and their hierarchyUxMenuTheme- Type for theme variants (light, dark)
Configuration Persistence
The UX Menu uses a similar persistence architecture to the UX Dashboard:
IndexedDB Storage
- Primary storage for menu configurations
- Configurations identified by unique menu IDs
- Observable updates when configurations change
Source Tracking
- Prevents circular updates between components
- Each save operation includes a source identifier
- Components can filter out updates from themselves
Implementation Details
Menu Item Structure
Menu items are structured hierarchically using the UxMenuItem interface:
interface UxMenuItem {
id: string;
label: string;
icon?: string;
route?: string;
badge?: string;
badgeColor?: string;
expanded?: boolean;
children?: UxMenuItem[];
disabled?: boolean;
visible?: boolean;
data?: any;
}
This structure allows for:
- Multiple levels of nesting with the
childrenproperty - Visual indicators with the
badgeproperty - Conditional display with the
visibleproperty - Custom data with the
dataproperty
Menu Configuration
The menu is configured using the UxMenuConfig interface:
interface UxMenuConfig {
id: string;
theme?: 'light' | 'dark';
position?: 'left' | 'right' | 'top' | 'bottom';
showIcons?: boolean;
showBadges?: boolean;
allowMultipleExpanded?: boolean;
animationDuration?: number;
closeOnNavigation?: boolean;
hasOverlay?: boolean;
overlayOpacity?: number;
backdropBlur?: boolean;
zIndex?: number;
persistConfig?: boolean;
autoLoadSavedConfig?: boolean;
}
Storage Integration
The menu integrates with the storage service to save and load configurations:
// Saving a configuration
this.menuStorageService.saveMenuConfig(
this.menuId,
this.menuConfig(),
this.menuItems(),
'menu-component-id'
).subscribe({
next: success => {
console.log('Menu configuration saved successfully');
}
});
// Loading a configuration
this.menuStorageService.loadMenuConfig(this.menuId).subscribe({
next: storedItem => {
if (storedItem) {
this.menuConfig.set(storedItem.config);
this.menuItems.set(storedItem.items);
}
}
});
Preventing Circular Updates
Like the UX Dashboard, the UX Menu implements strategies to prevent circular updates:
Source Tracking
// When saving a configuration this.menuStorageService.saveMenuConfig( menuId, config, items, 'menu-component-id' ); // When receiving updates this.menuStorageService.configUpdated$.subscribe(update => { if (update.source === 'menu-component-id') { return; // Ignore own updates } // Process update });Hash-Based Change Detection
// Generate hash of current config const configHash = this.generateConfigHash(config, items); // Skip save if unchanged if (configHash === this.lastSavedHash) { return; }
Event-Based Communication
The UX Menu uses RxJS subjects for event-based communication:
// In the storage service
private configUpdatedSubject = new Subject<{item: UxMenuStorageItem, source: string}>();
public configUpdated$ = this.configUpdatedSubject.asObservable();
// When saving
this.configUpdatedSubject.next({ item: storageItem, source });
// In the components
this.menuStorageService.configUpdated$.pipe(
takeUntil(this.destroy$)
).subscribe(update => {
// Handle update
});
Usage Example
Here's how to use the UX Menu component:
// In your component class
menuConfig = signal<UxMenuConfig>({
id: 'main-menu',
theme: 'light',
showIcons: true,
showBadges: true,
persistConfig: true
});
menuItems = signal<UxMenuItem[]>([
{
id: 'dashboard',
label: 'Dashboard',
icon: 'dashboard',
route: '/dashboard'
},
{
id: 'users',
label: 'Users',
icon: 'people',
children: [
{
id: 'user-list',
label: 'User List',
route: '/users/list'
},
{
id: 'user-groups',
label: 'User Groups',
route: '/users/groups',
badge: '3',
badgeColor: '#ff4081'
}
]
}
]);
// Handle menu item selection
onMenuItemSelected(item: UxMenuItem): void {
console.log('Selected menu item:', item);
// Navigate or perform action
}
<!-- In your component template -->
<app-ux-menu
[id]="'main-menu'"
[config]="menuConfig()"
[items]="menuItems()"
(itemSelected)="onMenuItemSelected($event)">
</app-ux-menu>
Lessons Learned
Our implementation of the UX Menu system taught us several valuable lessons:
Consistent patterns across widgets
- Using similar patterns for the menu and dashboard simplified development
- Common approaches to storage, configuration, and updates reduced code duplication
Selective item expansion
- Initially, expanding a menu item would collapse all others
- Adding the
allowMultipleExpandedoption provided needed flexibility
Proper cleanup is essential
- Using
takeUntil(destroy$)for all subscriptions prevents memory leaks - Ensuring all listeners are removed on component destruction is crucial
- Using
Troubleshooting
Menu Configuration Not Persisting
If menu configurations aren't persisting:
- Check that the
persistConfigproperty is set totruein the menu configuration - Ensure that the
UxMenuStorageServiceis properly initialized - Verify that the menu ID is consistent and unique
Menu Items Not Expanding/Collapsing
If menu items aren't expanding or collapsing properly:
- Check that the
childrenproperty is correctly defined for parent items - Verify that the
expandedproperty is being properly toggled - Ensure that the
allowMultipleExpandedconfiguration is set appropriately
Best Practices
Organize menu items hierarchically
const menuItems = [ { id: 'parent', label: 'Parent Item', children: [ { id: 'child1', label: 'Child 1' }, { id: 'child2', label: 'Child 2' } ] } ];Use badges sparingly
- Badges should convey important information (like counts or status)
- Too many badges can create visual noise
Implement proper source tracking
menuStorageService.saveMenuConfig(id, config, items, 'unique-component-id');Always unsubscribe from observables
ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); }