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
widget-library.component.tsis in@widgetslibrary- It needs to use
WidgetRegistryServicefrom@demos-lib - But
@demos-libdepends on@libs-foundational/core-modelswhich@widgetsalso uses - This creates a circular dependency:
@widgets→@demos-lib→@libs-foundational/core-models→@widgets
Previous Failed Approaches:
- ❌ Direct import: Causes circular dependency at build time
- ❌ Dynamic injection via
Injector: Services not available atngOnInittime - ❌ Dynamic import: Not supported in bundled Angular apps
- ❌ 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
✅ No Circular Dependencies
@widgetsnever imports from@demos-lib- Access happens at runtime via global window object
✅ Type Safety
- TypeScript still validates the registry methods
- Fallback to OLD registry if NEW not available
✅ Backwards Compatible
- OLD registry (
widget-library.service.ts) still works - NEW registry is used when available
- OLD registry (
✅ Simple & Explicit
- Clear intent: "This is globally accessible"
- Easy to debug: just check
window.__widgetRegistryService__
🔄 What's Next?
Phase 1: ✅ Complete - Current Implementation
- Duplicate Button Widget fixed
- Global registry exposed on window
- Widget library component uses global registry
- Libraries rebuilt
Phase 2: 🎯 Widget Variants Display (PENDING)
- Update widget-library UI to show all 12 Button variants
- Add variant cards with preview images
- Enable variant selection/drag-and-drop
Phase 3: 🎯 Widgets Management Route (PENDING)
- Create
/editor/:universe/widgetsroute - Build "Widgets Manager" component
- Show all widgets in the space with their variants
- Enable widget creation/editing
Phase 4: 🎯 Space-Scoped Widgets (PENDING)
- Implement space-level widget storage
- Allow widgets to be reused across universes
- Add widget sharing/permissions
📊 Files Changed
libs/demos-lib/src/lib/services/widget-registry.service.ts
- Added constructor to expose service on
window.__widgetRegistryService__
- Added constructor to expose service on
libs/widgets/src/lib/widgets/widget-library/widget-library.component.ts
- Updated
ngOnInit()to access registry viawindow.__widgetRegistryService__ - Added fallback with retry logic
- Updated
apps/gograb-space/src/main.ts
- Fixed duplicate widget registration by using only
widgetModuleLoader.getAllManifests()
- Fixed duplicate widget registration by using only
🧪 Testing
Refresh the browser and check the console for:
✅ [WidgetRegistry] 🌍 Service registered globally ✅ [WidgetLibrary] Found NEW registry via window.__widgetRegistryService__Open the widget library panel in the editor
You should see:
- Button Widget (with 12 variants indicator)
- No more duplicate widgets
- NEW registry being used
💡 Lessons Learned
- Dynamic injection doesn't work in Angular when services are in circular dependencies
- Global window variables are a valid pattern for breaking circular deps in Angular
- TypeScript's module system is build-time, not runtime - dynamic imports don't help
- 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! 🚀