Ux Dashboard

Ecosistema completo para organización y gestión de contenido con interfaces espaciales

UX Dashboard Component

The UX Dashboard is a versatile layout component that provides a consistent, customizable UI framework for applications. It includes a header, sidebar navigation, and content area, with theme support and persistent configuration.

Core Features

Architecture

The UX Dashboard consists of several interconnected components and services:

Components

Services

Models

Configuration Persistence

The dashboard uses a robust persistence system to maintain configuration state across page refreshes:

  1. IndexedDB Storage

    • Primary storage mechanism for configurations
    • Handles multiple dashboard instances with unique IDs
    • Provides observable updates when configurations change
  2. localStorage Fallback

    • Used when IndexedDB is unavailable
    • Provides a backup copy of configurations
  3. Source Tracking

    • Prevents circular update loops
    • Each save operation includes a source ID
    • Components can ignore updates from themselves

Implementing Theme Persistence

One of the key challenges in the dashboard system was ensuring theme persistence across page refreshes without causing update loops. Here's how we solved it:

The Challenge

When a dashboard component's theme changed, it would:

  1. Update its local configuration
  2. Save the configuration to IndexedDB
  3. Emit an update event
  4. Other components would detect this update and change their themes
  5. Those components would then save their configurations
  6. This would trigger more update events, creating an infinite loop

The Solution

We implemented a multi-layered approach to solve this issue:

  1. Source Tracking

    // When saving a configuration, identify the source
    this.dashboardStorageService.saveDashboardConfig(
      dashboardId,
      currentConfig,
      sections,
      'component-identifier'
    );
    
    // When receiving updates, ignore your own
    if (update.source === 'component-identifier') {
      console.log('Ignoring update from self');
      return;
    }
    
  2. Hash-Based Change Detection

    // Create a hash of the current configuration
    const configString = JSON.stringify({
      config: currentConfig,
      sections: sections
    });
    const currentHash = this.hashString(configString);
    
    // Skip saving if unchanged
    if (currentHash === this.lastSavedConfigHash) {
      console.log('No changes detected, skipping save');
      return;
    }
    
  3. Priority Theme Updates

    // Special handling for theme updates
    if (update.source === 'theme-change-immediate') {
      // Apply theme immediately without triggering further saves
      this.updateTheme(theme, false); // false means don't save
      return; // Skip further processing
    }
    
  4. Selective Update Application

    // Update the theme with an option to skip saving
    private updateTheme(theme: UxDashboardTheme, shouldSave: boolean = true): void {
      // Update the dashboard configuration
      this.dashboardConfig.update(config => ({
        ...config,
        theme: theme
      }));
    
      // Apply theme to body
      this.applyThemeToBody(theme);
    
      // Save only if requested
      if (shouldSave) {
        this.saveConfiguration();
      }
    }
    

Usage Example

Here's how theme changes are handled in the UxDashboardDemoComponent:

onThemeSelected(event: { groupId: string; value: string }): void {
  if (event.groupId === 'theme') {
    // Update configuration with new theme
    const updatedConfig = { ...this.dashboardConfig() };
    updatedConfig.theme = event.value as UxDashboardTheme;
    this.dashboardConfig.set(updatedConfig);

    // Special handling for main dashboard
    if (this.customDashboardId() === 'main-ux-dashboard') {
      // Use a special source identifier for theme changes
      this.dashboardStorageService.saveDashboardConfig(
        'main-ux-dashboard',
        updatedConfig,
        this.dashboardSections(),
        'theme-change-immediate'
      ).subscribe();
    } else {
      // Normal save for other dashboards
      this.saveConfiguration();
    }
  }
}

Lessons Learned

Through our implementation of the UX Dashboard system, we learned several important lessons:

  1. Event-based updates are better than polling

    • Initially, we used interval-based polling to check for configuration changes
    • Switching to an event-based system using RxJS subjects improved performance and reliability
  2. Identify update sources to prevent loops

    • Including a source identifier with each update allows components to ignore their own updates
    • This prevents circular update chains that can cause infinite loops
  3. Create clear ownership boundaries

    • The main dashboard (main-ux-dashboard) needed special handling
    • Establishing clear ownership and priority rules for updates resolved conflicts
  4. Implement hash-based change detection

    • Avoid unnecessary saves by checking if configurations have actually changed
    • Simple hash functions can dramatically reduce database operations

Troubleshooting

Theme Not Persisting After Refresh

If themes aren't persisting across page refreshes:

  1. Check that the persistUIState property is set to true in the dashboard configuration
  2. Verify that the UxDashboardStorageService is properly initialized
  3. Look for error messages related to IndexedDB access in the console
  4. Ensure that the dashboard ID is consistent across refreshes

Circular Update Loops

If you're experiencing infinite update loops:

  1. Check that components are properly tracking the source of updates
  2. Ensure components ignore updates that originated from themselves
  3. Verify that hash-based change detection is working correctly
  4. Add console logs to track the flow of updates through the system

Best Practices

  1. Always specify a unique dashboard ID

    <app-ux-dashboard [id]="'my-unique-dashboard-id'"></app-ux-dashboard>
    
  2. Use signals for reactive state management

    dashboardConfig = signal<UxDashboardConfig>({...});
    
  3. Include source identifiers when saving

    storageService.saveDashboardConfig(id, config, sections, 'my-component-id');
    
  4. Implement selective update handling

    if (update.source !== myComponentId && needsProcessing(update)) {
      // Process the update
    }