Widget vs Module Architecture - Definitive Guide

Clear distinction between widgets (simple UI components) and modules (complex building blocks) for platform-agnostic development

Widget vs Module Architecture - Definitive Guide

Critical Decision: Clear distinction between simple UI components (widgets) and complex building blocks (modules) enables platform-agnostic development and maximum reusability.


Core Principle

Everything that needs to be platform-agnostic becomes a module. Simple UI elements stay as widgets.

This allows us to:


Widgets (Simple UI Components)

Definition

Widgets are pure presentation components with no lifecycle, routes, or navigation. They are Angular-specific UI building blocks.

Characteristics

Storage Location

Examples

// ✅ WIDGET: Simple button component
@Component({
  selector: 'ux-button',
  standalone: true,
  // No BrowserModuleContract
  // No routes
  // No lifecycle methods
})
export class UxButtonComponent {
  @Input() label: string = '';
  @Input() variant: 'primary' | 'secondary' = 'primary';
  @Output() clicked = new EventEmitter<void>();
}

Widget Examples:

Usage

Widgets are used:

  1. In Editor: Drag-and-drop onto pages
  2. In Modules: As embeddable components via manifest.embeddableComponents[]
  3. Standalone: Directly in Angular templates

Modules (Complex Building Blocks)

Definition

Modules are complex, platform-agnostic building blocks that implement BrowserModuleContract with full lifecycle management.

Characteristics

Storage Location

Examples

// ✅ MODULE: Sidebar navigation module
@ModuleDecorator({
  id: 'sidebar-nav',
  version: '1.0.0',
  name: 'Sidebar Navigation',
  // Can have routes, permissions, navigation
})
@Injectable({ providedIn: 'root' })
export class SidebarNavModule implements BrowserModuleContract {
  readonly manifest: ModuleManifest = {
    id: 'sidebar-nav',
    version: '1.0.0',
    name: 'Sidebar Navigation',
    
    // ✅ Can have routes (empty for sidebar, but could have)
    routes: [],
    
    // ✅ Can expose widgets as embeddable components
    embeddableComponents: [
      {
        id: 'sidebar-nav-widget',
        componentType: SidebarNavComponent, // The actual widget
        displayName: 'Sidebar Navigation',
        category: 'widget',
      }
    ],
    
    // ✅ Can have public actions
    publicActions: {},
    
    // ✅ Can have permissions
    permissions: [],
  };
  
  // ✅ Full lifecycle methods
  configure<T>(config: T): Observable<void> { ... }
  initialize(): Observable<void> { ... }
  start(): Observable<void> { ... }
  stop(): Observable<void> { ... }
  destroy(): Observable<void> { ... }
}

Module Examples:

The Bridge: EmbeddableComponents

Key Insight: Modules expose widgets through manifest.embeddableComponents[], allowing behavioral modules to provide visual components.

// Module exposes its component as an embeddable widget
embeddableComponents: [
  {
    id: 'sidebar-nav-widget',
    componentType: SidebarNavComponent, // ← The actual Angular component
    displayName: 'Sidebar Navigation',
    category: 'widget',
  }
]

This allows:


Decision Tree: Widget vs Module?

Use Widget When:

  1. ✅ Pure UI Component: Only displays data or handles simple input
  2. ✅ No Routes Needed: Component doesn't need navigation
  3. ✅ No Lifecycle Needed: Component doesn't need initialization/cleanup
  4. ✅ Angular-Specific: Will only be used in Angular apps
  5. ✅ Simple Reusability: Just needs to be used in templates

Examples: Button, Input, Card, Badge, Chip, Icon, Tabs

Use Module When:

  1. ✅ Needs Lifecycle: Requires initialization, configuration, cleanup
  2. ✅ Needs Routes: Defines navigation routes
  3. ✅ Needs Permissions: Requires access control
  4. ✅ Needs Public API: Exposes actions for other modules
  5. ✅ Platform-Agnostic: Must work in Angular AND Flutter (future)
  6. ✅ Complex Behavior: Has state management, services, business logic
  7. ✅ Composition: Contains other modules or needs to be composed

Examples: Sidebar-nav, TopNav, Footer, User Management, Space Management

Gray Area: When to Convert Widget → Module?

Convert a widget to a module when:

  1. Needs Platform-Agnostic Support: Will be used in Flutter later
  2. Needs Configuration: Requires runtime configuration
  3. Needs Routes: Component needs to define navigation
  4. Needs Permissions: Requires access control
  5. Needs Public Actions: Other modules need to interact with it
  6. Becomes Complex: Has services, state management, business logic

Example:


Architecture Benefits

1. Platform-Agnostic Development

// Module can be implemented in Angular (now)
@ModuleDecorator({ id: 'sidebar-nav' })
export class SidebarNavModule implements BrowserModuleContract {
  // Angular implementation
}

// Same module can be implemented in Flutter (future)
class SidebarNavModule implements FlutterModuleContract {
  // Flutter implementation
  // Same manifest, same behavior, different platform
}

2. Maximum Reusability

3. Clear Separation of Concerns

4. Infinite Composition

// Module can contain other modules
Universe Module
  ├── Sidebar Module (navigation)
  ├── TopNav Module (navigation)
  ├── Content Module (galaxy)
  │   ├── World Module (page container)
  │   │   └── Page Module (content)
  └── Footer Module (navigation)

Migration Guide

Converting Widget → Module

Step 1: Create module file in modules/structural/{name}/

Step 2: Implement BrowserModuleContract

@ModuleDecorator({
  id: 'my-module',
  version: '1.0.0',
  name: 'My Module',
})
@Injectable({ providedIn: 'root' })
export class MyModule implements BrowserModuleContract {
  readonly manifest: ModuleManifest = {
    id: 'my-module',
    version: '1.0.0',
    name: 'My Module',
    
    // Expose the widget as embeddable
    embeddableComponents: [
      {
        id: 'my-module-widget',
        componentType: MyWidgetComponent, // ← Original widget
        displayName: 'My Widget',
        category: 'widget',
      }
    ],
  };
  
  // Implement lifecycle methods
  configure<T>(config: T): Observable<void> { return of(void 0); }
  initialize(): Observable<void> { return of(void 0); }
  start(): Observable<void> { return of(void 0); }
  stop(): Observable<void> { return of(void 0); }
  destroy(): Observable<void> { return of(void 0); }
}

Step 3: Register module in apps/gograb-space/src/app/core/modules.ts

Step 4: Update imports - use module instead of widget directly


Current State Audit

Widgets (Stay as Widgets)

ux-ui-core:

ux-ui-extended:

ux-ui-forms:

ux-ui-feedback:

Modules (Already Modules)

modules/structural/:

modules/new-user-management/:

modules/space-management/:

Potential Conversions (Widget → Module)

To Be Evaluated:


Best Practices

1. Start Simple

2. Keep Widgets Pure

3. Modules Expose Widgets

4. Document Decisions


Summary

Aspect Widget Module
Lifecycle ❌ No ✅ Yes (BrowserModuleContract)
Routes ❌ No ✅ Yes
Navigation ❌ No ✅ Yes
Permissions ❌ No ✅ Yes
Public Actions ❌ No ✅ Yes
Platform-Agnostic ❌ No (Angular only) ✅ Yes (Angular + Flutter)
Composition ❌ No ✅ Yes (can contain modules)
Storage libs/libs-ui/* modules/*
Use Case Simple UI components Complex building blocks

The Bridge: Modules expose widgets via manifest.embeddableComponents[], allowing behavioral modules to provide visual components.


This architecture enables the most advanced, flexible, and platform-agnostic platform builder in the universe! 🚀