Module Registration System

Complete guide to module registration (visual and code-first)

Module Registration System

Updated: December 3, 2025
Status: ✅ Production System - Visual + Code-First
Purpose: Complete guide to module registration using both approaches


🎯 Executive Summary

The Production System: All modules in GoGrab Space use the BrowserModuleContract pattern with @ModuleDecorator for registration.

Two Creation Methods, One Standard:

  1. Visual Builder (modularity-lib) - Generates BrowserModuleContract code
  2. Code-First - Write BrowserModuleContract code manually

Both produce the exact same result: production-ready modules implementing BrowserModuleContract.

Key Facts:

Module Creation Tools:


📊 Production System: BrowserModuleContract

📚 Source of Truth: All patterns must match gograb-modules/modules/new-user-management/
🔍 Verify: Check gograb-modules implementation before following examples
⚠️ Updated: November 23, 2025 - Fixed array format for publicActions and embeddableComponents

Overview

Location: @libs-core/module-base
Used By: gograb-admin (production), gograb-space (migrating)
Status: ✅ Production-Ready

Architecture

// 1. Define a class implementing BrowserModuleContract
@Injectable({ providedIn: 'root' })
@ModuleDecorator({
  id: 'my-module',
  version: '1.0.0',
  name: 'My Module',
  description: 'Module description'
})
export class MyModule implements BrowserModuleContract {
  readonly manifest: ModuleManifest = {
    id: 'my-module',
    name: 'My Module',
    version: '1.0.0',
    description: 'Module description',
    routes: [...],
    navigation: {...},
    publicActions: [  // ✅ Array format (not object)
      NavigateToItemAction,
      CreateItemAction,
      UpdateItemAction,
    ],
    embeddableComponents: [  // ✅ Array format (not object)
      {
        id: 'my-module:list-widget',
        componentType: MyListWidgetComponent,
        displayName: 'My List Widget',
        description: 'Displays a list of items',
        category: 'widget',
        inputs: [
          { name: 'limit', type: 'number', required: false },
        ],
        outputs: [
          { name: 'itemSelected', type: 'object' },
        ],
      },
    ]
  };
  
  configure<T = Record<string, unknown>>(config: T): Observable<void> {
    void config;
    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); }
}

// 2. Import in central registry (apps/gograb-admin/src/app/core/modules.ts)
import { MyModule } from '@my-module';
const coreModules = [MyModule];
void coreModules; // Prevents tree-shaking

// 3. Import registry in app.config.ts
import './core/modules'; // Triggers @ModuleDecorator registration

// 4. Auto-discovery handles the rest
// - ModuleDiscoveryService finds all @ModuleDecorator classes
// - ModuleRegistryService stores them
// - BrowserBootstrapperService manages lifecycle
// - RouteRegistrationService injects routes dynamically

Key Files

Core Infrastructure:

Registration Flow

graph TB
    A[@ModuleDecorator applied to class] --> B[Decorator registers in static registry]
    B --> C[ModuleDiscoveryService.discoverAndRegister]
    C --> D[ModuleRegistryService.register]
    D --> E[BrowserBootstrapperService initializes lifecycle]
    E --> F[RouteRegistrationService injects routes]
    F --> G[Module fully operational ✅]

Characteristics

Aspect Details
Auto-Discovery ✅ Yes - via @ModuleDecorator
Lifecycle Management ✅ Full (configure → initialize → start → stop → destroy)
Route Injection ✅ Dynamic route registration
Enterprise Features ✅ Multi-tenant, configuration API, observability
Complexity Medium (requires class implementation + lifecycle hooks)
Boilerplate Moderate (offset by comprehensive features)
Production Status ✅ PRODUCTION-READY
Used In gograb-admin ✅, gograb-space (migrating) 🚧

Working Example (gograb-admin)

File: apps/gograb-admin/src/app/core/modules.ts

/**
 * Module registration file.
 * Import all modules here to trigger registration
 * with the module-integrator system via their @ModuleDecorator decorators.
 */

import { UserManagementModule } from '@libs-core/user-management';

const coreModules = [UserManagementModule];

// Prevent tree-shaking
void coreModules;

File: apps/gograb-admin/src/app/app.config.ts

import './core/modules'; // ⭐ Triggers module registration

export const appConfig: ApplicationConfig = {
  providers: [
    // ... other providers
    provideBootstrapInitializer(), // Initializes modules
    // ...
  ]
};

Result: Module auto-discovered, routes injected, fully operational ✅


🚫 Deprecated Experiment: @module-system (Manifest-Only)

Overview

Status: ⚠️ Incomplete Experiment - Do Not Use
Location: libs/module-system (deprecated)
Attempted In: gograb-space only

Why It Exists

An attempt was made in gograb-space to simplify module registration by using plain manifests instead of full class implementations. However:

What Was Attempted

// Simplified manifest-only approach (INCOMPLETE)
export const MyModuleManifest: ModuleManifest = {
  id: 'my-module',
  name: 'My Module',
  version: '1.0.0',
  routes: [...],
  navigation: {...}
};

// Manual registration in main.ts (DOESN'T WORK FULLY)
const moduleRegistry = inject(ModuleRegistryService);
moduleRegistry.registerModule(MyModuleManifest);

Why It Failed

  1. No Lifecycle Hooks: Can't manage initialization, startup, shutdown
  2. Route Injection Broken: Routes not dynamically injected
  3. No Auto-Discovery: Requires manual registration everywhere
  4. Incomplete Implementation: Never finished integrating with core system
  5. Dual System Confusion: Created confusion about which system to use

Current Status

Verdict: ❌ Do Not Use - Use BrowserModuleContract instead

Migration Path: All gograb-space modules should migrate to BrowserModuleContract pattern (see Migration Guide below)


🎨 Registration Method 1: Visual Builder

Access the Module Workbench

  1. Navigate to /modularity in GoGrab Space
  2. Click "New Module"
  3. Follow the 6-step guided process
  4. Export as TypeScript when complete

What Gets Generated

The Module Workbench automatically generates:

Export and Integrate

  1. Export: Click "Export as Angular Library" in Deploy step
  2. Save: Download the generated files
  3. Place: Copy to modules/your-module/ in your monorepo
  4. Build: Run nx build your-module
  5. Register: Add to apps/gograb-space/src/app/core/modules.ts

Generated Code Quality:


💻 Registration Method 2: Code-First

Step 1: Generate Library

npx nx g @nx/angular:lib my-module --directory=modules --standalone

Note: All GoGrab Space modules use the @module/ namespace prefix. See Naming Convention for details.

Step 2: Implement BrowserModuleContract

File: modules/my-module/src/lib/my-module.module.ts

import { Injectable, inject } from '@angular/core';
import { Observable, of } from 'rxjs';
import { BrowserModuleContract, ModuleManifest, ModuleDecorator } from '@libs-core/module-base';
import { LoggerService } from '@libs-core/log-system';

@ModuleDecorator({
  id: 'my-module',
  version: '1.0.0',
  displayName: 'My Module'
})
@Injectable({ providedIn: 'root' })
export class MyModule implements BrowserModuleContract {
  private readonly logger = inject(LoggerService);

  readonly manifest: ModuleManifest = {
    id: 'my-module',
    version: '1.0.0',
    displayName: 'My Module',
    routes: [
      {
        path: 'my-module',
        loadComponent: () => import('./components/my-module.component')
          .then(m => m.MyModuleComponent)
      }
    ],
    navigation: {
      menuItems: [{
        id: 'my-module',
        label: 'My Module',
        route: '/my-module',
        icon: 'dashboard'
      }]
    }
  };

  configure(config: any): Observable<void> {
    this.logger.info('MyModule', 'Configuring...', config);
    return of(void 0);
  }

  initialize(): Observable<void> {
    this.logger.info('MyModule', 'Initializing...');
    return of(void 0);
  }

  start(): Observable<void> {
    this.logger.info('MyModule', 'Starting...');
    return of(void 0);
  }

  stop(): Observable<void> {
    this.logger.info('MyModule', 'Stopping...');
    return of(void 0);
  }

  destroy(): Observable<void> {
    this.logger.info('MyModule', 'Destroying...');
    return of(void 0);
  }
}

Step 3: Register Module

File: apps/gograb-admin/src/app/core/modules.ts (or equivalent)

import { MyModule } from '@my-module';

const coreModules = [
  UserManagementModule,
  MyModule  // ⭐ Add your module here
];

void coreModules;

Step 4: Import in App Config

File: apps/gograb-admin/src/app/app.config.ts

import './core/modules'; // ✅ Already done

Step 5: Run and Verify

# Start the app
npx nx serve gograb-admin

# Check console logs:
# [MyModule] Configuring...
# [MyModule] Initializing...
# [MyModule] Starting...

Result: Module auto-discovered, routes available, navigation shows up! ✅


🔄 Migration Guide (gograb-space)

Current State

gograb-space attempted to use manifest-only registration but it's incomplete.

Migration Steps

1. Create BrowserModuleContract Classes

For each module in gograb-space, convert from:

// ❌ OLD (incomplete)
export const SingularityManifest: ModuleManifest = { ... };

To:

// ✅ NEW (production)
@ModuleDecorator({ id: 'singularity', version: '1.0.0' })
@Injectable({ providedIn: 'root' })
export class SingularityModule implements BrowserModuleContract {
  readonly manifest: ModuleManifest = { ... };
  
  configure() { ... }
  initialize() { ... }
  start() { ... }
  stop() { ... }
  destroy() { ... }
}

2. Create Module Registry File

File: apps/gograb-space/src/app/core/modules.ts

import { SingularityModule } from '@libs-structural/singularity-lib';
import { SpaceMapModule } from '@space-map-lib';
import { EditorModule } from '@editor-web-lib';
// ... other modules

const platformModules = [
  SingularityModule,
  SpaceMapModule,
  EditorModule
  // ... other modules
];

void platformModules; // Prevents tree-shaking

3. Import in App Config

File: apps/gograb-space/src/app/app.config.ts

import './core/modules'; // ⭐ Add this line

export const appConfig: ApplicationConfig = {
  providers: [
    // ... existing providers
  ]
};

4. Remove Manual Registration

File: apps/gograb-space/src/main.ts

// ❌ REMOVE these manual registrations
// provideAppInitializer(() => {
//   const moduleRegistry = inject(ModuleRegistryService);
//   moduleRegistry.registerModule(SingularityManifest);
//   moduleRegistry.registerModule(SpaceMapManifest);
//   // ...
// });

5. Test Thoroughly

# Run gograb-space
npx nx serve gograb-space

# Verify in console:
# - Module discovery logs
# - Route registration
# - Navigation items appear

📚 Reference Documentation

Source of Truth

Related Documentation


🚨 Important Lessons Learned

The Confusion (October 2025)

We discovered documentation labeled the systems backwards:

Reality:

How to Prevent This

  1. Trust the Source: gograb-modules team is the foundation - follow their patterns
  2. Verify with Code: Check working implementations (gograb-admin)
  3. Document Immediately: Real-time documentation prevents confusion
  4. Complete Migrations: Don't leave systems half-migrated
  5. Archive Deprecated Docs: Clear labeling of what's outdated

✅ Best Practices

Module Development

  1. Always implement BrowserModuleContract - No shortcuts
  2. Use @ModuleDecorator - Enables auto-discovery
  3. Implement all lifecycle hooks - Even if some are empty
  4. Follow gograb-modules patterns - They're the source of truth
  5. Test module lifecycle - Verify configure → start → stop → destroy

Registration

  1. Use central registry file (core/modules.ts)
  2. Import in app.config.ts - Triggers auto-discovery
  3. Let the system handle it - Don't manually register
  4. Check console logs - Verify discovery and initialization
  5. Test routes dynamically - Ensure route injection works

Documentation

  1. Document as you build - Real-time is best
  2. Link to source of truth - Reference gograb-modules docs
  3. Show working examples - Link to gograb-admin
  4. Update when patterns change - Keep docs current
  5. Archive deprecated - Don't delete, explain why it changed

📞 Questions?

If you're confused about module registration:

✅ Use: BrowserModuleContract with @ModuleDecorator
✅ Example: gograb-admin application
✅ Reference: GoGrab-modules documentation
❌ Don't Use: @module-system manifest-only registration

For Help: See Module Creation Guide


🎨 The Visual Builder Advantage

Why Use the Module Workbench?

  1. Guided Process: Never forget a step or misconfigure
  2. AI Assistance: Describe your module, AI generates structure
  3. Instant Validation: Real-time checks for completeness
  4. Code Generation: Production-ready TypeScript output
  5. Best Practices: Generated code follows all standards
  6. Flash Persistence: Your work auto-saves with real-time sync
  7. Entity-Aware: Actions and widgets understand your data model
  8. Visual Flow Builder: Build action logic without writing RxJS operators

When to Add Manual Code

After exporting from the visual builder, you might manually add:

The 80/20 Rule: Visual builder handles 80% of the structure, you refine the 20% of complex logic.


Last Updated: December 3, 2025 - 9:50 PM EST Status: ✅ Current - Hybrid Approach (Visual + Code) Version: 3.0 (Visual Builder Integration) Session ID: 3