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:
- Build once, deploy to Angular (web) and Flutter (mobile) later
- Maintain clear separation of concerns
- Enable maximum reusability
- Support infinite composition (modules contain modules)
Widgets (Simple UI Components)
Definition
Widgets are pure presentation components with no lifecycle, routes, or navigation. They are Angular-specific UI building blocks.
Characteristics
- ✅ Pure Presentation: Display data, handle user input
- ✅ No Lifecycle: No BrowserModuleContract implementation
- ✅ No Routes: Cannot define navigation routes
- ✅ No Permissions: No access control logic
- ✅ No Public Actions: Cannot expose programmatic APIs
- ✅ Angular-Specific: Built for Angular, not platform-agnostic
- ✅ Reusable: Can be used in Editor, modules, and other components
Storage Location
libs/libs-ui/ux-ui-core/- Core widgets (Button, Input, Card, etc.)libs/libs-ui/ux-ui-extended/- Extended widgetslibs/libs-ui/ux-ui-forms/- Form widgetslibs/libs-ui/ux-ui-feedback/- Feedback widgets (Toast, Alert)
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:
- Button, Input, Card, Badge, Chip, Breadcrumb
- Icon, Tabs, Context Menu, Editable Slug
- Form inputs, Select, Checkbox, Radio
- Toast, Alert, Loading Spinner
Usage
Widgets are used:
- In Editor: Drag-and-drop onto pages
- In Modules: As embeddable components via
manifest.embeddableComponents[] - 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
- ✅ Full Lifecycle: Implements
BrowserModuleContract(configure, initialize, start, stop, destroy) - ✅ Can Have Routes: Define navigation routes via
manifest.routes[] - ✅ Can Have Navigation: Menu items, breadcrumbs via
manifest.navigation - ✅ Can Have Permissions: Access control via
manifest.permissions[] - ✅ Can Have Public Actions: Expose programmatic APIs via
manifest.publicActions[] - ✅ Platform-Agnostic: Designed to work in Angular (now) and Flutter (future)
- ✅ Can Expose Widgets: Via
manifest.embeddableComponents[] - ✅ Composable: Modules can contain other modules (infinite nesting)
Storage Location
modules/structural/- Structural modules (Sidebar, TopNav, Footer)modules/new-user-management/- Feature modules (User Management)modules/space-management/- Admin modules
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:
- Structural: Sidebar-nav, TopNav, Footer (navigation components)
- Feature: User Management, Space Management (complex functionality)
- Content: Blog Module, E-commerce Module (domain-specific)
- Layout: Grid Module, Container Module (layout systems)
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:
- Sidebar Module (behavioral) → exposes SidebarNavComponent (visual widget)
- User Management Module (behavioral) → exposes UserListWidgetComponent (visual widget)
- Modules provide both behavior (lifecycle, routes, actions) and visual components (widgets)
Decision Tree: Widget vs Module?
Use Widget When:
- ✅ Pure UI Component: Only displays data or handles simple input
- ✅ No Routes Needed: Component doesn't need navigation
- ✅ No Lifecycle Needed: Component doesn't need initialization/cleanup
- ✅ Angular-Specific: Will only be used in Angular apps
- ✅ Simple Reusability: Just needs to be used in templates
Examples: Button, Input, Card, Badge, Chip, Icon, Tabs
Use Module When:
- ✅ Needs Lifecycle: Requires initialization, configuration, cleanup
- ✅ Needs Routes: Defines navigation routes
- ✅ Needs Permissions: Requires access control
- ✅ Needs Public API: Exposes actions for other modules
- ✅ Platform-Agnostic: Must work in Angular AND Flutter (future)
- ✅ Complex Behavior: Has state management, services, business logic
- ✅ 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:
- Needs Platform-Agnostic Support: Will be used in Flutter later
- Needs Configuration: Requires runtime configuration
- Needs Routes: Component needs to define navigation
- Needs Permissions: Requires access control
- Needs Public Actions: Other modules need to interact with it
- Becomes Complex: Has services, state management, business logic
Example:
- Widget: Simple Button → stays widget
- Module: Navigation Menu → becomes module (needs routes, permissions, platform-agnostic)
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
- Widgets: Reusable in Editor, modules, any Angular component
- Modules: Reusable across universes, composable, shareable
3. Clear Separation of Concerns
- Widgets: Handle presentation only
- Modules: Handle behavior, lifecycle, routing, permissions
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:
- Button, Input, Card, Badge, Chip, Breadcrumb, Icon, Tabs, Context Menu, Editable Slug
ux-ui-extended:
- Extended UI components (to be audited)
ux-ui-forms:
- Form inputs, Select, Checkbox, Radio, etc.
ux-ui-feedback:
- Toast, Alert, Loading Spinner
Modules (Already Modules)
modules/structural/:
- ✅ sidebar-nav - Module (exposes SidebarNavComponent as embeddable)
- ✅ top-nav - Module (exposes TopNavComponent as embeddable)
- ✅ footer - Module (exposes FooterComponent as embeddable)
modules/new-user-management/:
- ✅ User Management Module (full lifecycle, routes, permissions, actions)
modules/space-management/:
- ✅ Space Management Module (admin panel, data management)
Potential Conversions (Widget → Module)
To Be Evaluated:
- ux-menu - Could become module if needs routes/permissions
- ux-panel - Could become module if needs lifecycle/configuration
- ux-top-nav - Already a module pattern, verify consistency
Best Practices
1. Start Simple
- Start as Widget: If unsure, start as a widget
- Convert to Module: When you need lifecycle, routes, or platform-agnostic support
2. Keep Widgets Pure
- No Business Logic: Widgets should only handle presentation
- No Services: Widgets should not inject complex services
- No State Management: Use signals for local state only
3. Modules Expose Widgets
- Always Expose: Modules should expose their visual components as embeddable widgets
- Reusable: This allows widgets to be used independently OR as part of module
4. Document Decisions
- Why Widget?: Document why component is a widget
- Why Module?: Document why component is a module
- Future Plans: Document if widget will become module later
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! 🚀