WIDGET SYSTEM GLOBAL REGISTRY OCT 27 2025

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

🌍 Widget System: Global Registry Solution

Date: October 27, 2025
Status: ✅ IMPLEMENTED


🎯 Problem

Circular Dependency Between @widgets and @demos-lib

Previous Failed Approaches:

  1. ❌ Direct import: Causes circular dependency at build time
  2. ❌ Dynamic injection via Injector: Services not available at ngOnInit time
  3. ❌ Dynamic import: Not supported in bundled Angular apps
  4. ❌ Bridge service: Still causes circular dependency

✅ Solution: Global Window Registry

Expose the NEW registry on the global window object to avoid circular dependencies.

Implementation

1. Make WidgetRegistryService Globally Accessible

// libs/demos-lib/src/lib/services/widget-registry.service.ts

@Injectable({ providedIn: 'root' })
export class WidgetRegistryService {
  private readonly storage = inject(StorageService);
  
  // Internal state
  private readonly _widgets = signal<Map<WidgetType, RegisteredWidget>>(new Map());
  private readonly _favorites = signal<Set<WidgetType>>(new Set());
  private readonly _isLoaded = signal(false);
  
  constructor() {
    // 🌍 Make this service globally accessible to avoid circular dependency issues
    (window as any).__widgetRegistryService__ = this;
    console.log('[WidgetRegistry] 🌍 Service registered globally as window.__widgetRegistryService__');
    this.loadFromStorage();
  }
  
  // ... rest of service
}

2. Access Global Registry in Widget Library Component

// libs/widgets/src/lib/widgets/widget-library/widget-library.component.ts

ngOnInit(): void {
  console.log('🎨 Widget Library Component: Initializing...');

  // 🌍 Try to get NEW registry from global window variable (avoids circular dependency)
  if ((window as any).__widgetRegistryService__) {
    this.newRegistry = (window as any).__widgetRegistryService__;
    console.log('✅ [WidgetLibrary] Found NEW registry via window.__widgetRegistryService__');
  } else {
    console.warn('⚠️ [WidgetLibrary] NEW registry not found on window, using OLD registry as fallback');
    // Retry after a delay in case it's not initialized yet
    setTimeout(() => {
      if ((window as any).__widgetRegistryService__) {
        this.newRegistry = (window as any).__widgetRegistryService__;
        console.log('✅ [WidgetLibrary] Found NEW registry via window.__widgetRegistryService__ (delayed)');
        // Force update
        this.selectedCategorySignal.set(this.selectedCategorySignal());
      }
    }, 100);
  }
  
  // ... rest of initialization
}

🎯 Expected Behavior After Fix

When you refresh the browser, you should see:

✅ Good (What You Should See):

[WidgetRegistry] 🌍 Service registered globally as window.__widgetRegistryService__
[WidgetRegistry] 🎯 Successfully loaded 1 widget(s) from 1 module(s)
[WidgetLibrary] ✅ Found NEW registry via window.__widgetRegistryService__
[WidgetLibrary] ✨ Using NEW registry: 1 widgets

❌ Bad (If Still Broken):

[WidgetLibrary] ⚠️ NEW registry not found on window, using OLD registry as fallback
[WidgetLibrary] ⚠️ Using OLD registry: 13 widgets

📋 Architecture Benefits

  1. ✅ No Circular Dependencies

    • @widgets never imports from @demos-lib
    • Access happens at runtime via global window object
  2. ✅ Type Safety

    • TypeScript still validates the registry methods
    • Fallback to OLD registry if NEW not available
  3. ✅ Backwards Compatible

    • OLD registry (widget-library.service.ts) still works
    • NEW registry is used when available
  4. ✅ Simple & Explicit

    • Clear intent: "This is globally accessible"
    • Easy to debug: just check window.__widgetRegistryService__

🔄 What's Next?

Phase 1: ✅ Complete - Current Implementation

Phase 2: 🎯 Widget Variants Display (PENDING)

Phase 3: 🎯 Widgets Management Route (PENDING)

Phase 4: 🎯 Space-Scoped Widgets (PENDING)


📊 Files Changed

  1. libs/demos-lib/src/lib/services/widget-registry.service.ts

    • Added constructor to expose service on window.__widgetRegistryService__
  2. libs/widgets/src/lib/widgets/widget-library/widget-library.component.ts

    • Updated ngOnInit() to access registry via window.__widgetRegistryService__
    • Added fallback with retry logic
  3. apps/gograb-space/src/main.ts

    • Fixed duplicate widget registration by using only widgetModuleLoader.getAllManifests()

🧪 Testing

  1. Refresh the browser and check the console for:

    ✅ [WidgetRegistry] 🌍 Service registered globally
    ✅ [WidgetLibrary] Found NEW registry via window.__widgetRegistryService__
    
  2. Open the widget library panel in the editor

  3. You should see:

    • Button Widget (with 12 variants indicator)
    • No more duplicate widgets
    • NEW registry being used

💡 Lessons Learned

  1. Dynamic injection doesn't work in Angular when services are in circular dependencies
  2. Global window variables are a valid pattern for breaking circular deps in Angular
  3. TypeScript's module system is build-time, not runtime - dynamic imports don't help
  4. Simpler is better - tried 4 complex solutions before finding the simple one

Next Step: Test the global registry fix and verify Button Widget with variants is visible! 🚀