Canvas Template Module Pattern

Guide for creating layout template modules using BrowserModuleContract

Canvas Template Module Pattern

Status: ✅ Production Pattern - Use This!
First Implementation: Split Sidebar Template Module
Date: November 23, 2025


🎯 The Breakthrough

We've achieved true meta-modularity for layout templates!

Every layout template is now a full BrowserModuleContract module that:


📐 Template Module Structure

File Organization

modules/canvas-templates/{template-name}/
├── src/
│   ├── lib/
│   │   ├── {template-name}-template.module.ts    # Main module class
│   │   └── components/
│   │       └── {template-name}-renderer.component.ts  # Renderer
│   └── index.ts                                   # Barrel export
├── project.json                                   # NX configuration
├── tsconfig.lib.json                              # TypeScript config
└── README.md                                      # Documentation

Module Class Pattern

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

@ModuleDecorator({
  id: 'template-{name}',
  version: '1.0.0',
  displayName: '{Name} Template'
})
@Injectable({ providedIn: 'root' })
export class {Name}TemplateModule implements BrowserModuleContract {
  private readonly logger = inject(LoggerService).child({ source: '{Name}Template' });

  readonly manifest: ModuleManifest = {
    id: 'template-{name}',
    version: '1.0.0',
    displayName: '{Name} Template',
    description: '...',
    icon: '...',
    category: 'layout-template',
    tags: ['template', 'layout', ...],
    
    // Platform rendering instructions
    metadata: {
      type: 'layout-template',
      responsive: true,
      platforms: {
        web: {
          technology: 'CSS Grid',
          gridColumns: '...',
          gridRows: '...',
          gridAreas: '...'
        },
        flutter: {
          structure: '...',
          widgets: [...]
        }
      }
    },

    // Template regions
    regions: [
      {
        name: 'region-name',
        displayName: 'Region Display Name',
        description: '...',
        type: 'module-container',
        config: { width: '...', flex: true/false }
      }
    ],

    // Public actions API
    publicActions: {
      configure: (config) => {...},
      exportJSON: () => {...},
      getRenderingInstructions: (platform) => {...}
    },

    // Embeddable renderer
    embeddableComponents: {
      'template-renderer': {
        component: {Name}RendererComponent,
        inputs: ['regions', 'config'],
        outputs: ['regionClicked', 'moduleDropped'],
        description: '...'
      }
    }
  };

  // Lifecycle hooks
  async configure(config: ModuleConfiguration): Promise<void> {}
  async initialize(): Promise<void> {}
  async start(): Promise<void> {}
  async stop(): Promise<void> {}
  async destroy(): Promise<void> {}
}

Renderer Component Pattern

import { Component, input, output, computed, ChangeDetectionStrategy } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'lib-{name}-renderer',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div class="{name}-template" [ngStyle]="containerStyles()">
      <!-- Render each region -->
      @for (region of regions() | keyvalue; track region.key) {
        <div 
          class="template-region"
          [attr.data-region]="region.key"
          [ngStyle]="getRegionStyles(region.key)"
          (click)="regionClicked.emit(region.key)">
          
          <!-- Region label -->
          <div class="region-label">
            <span class="region-name">{{ region.value.displayName }}</span>
          </div>

          <!-- Region content (modules) -->
          <div class="region-content">
            @if (region.value.modules?.length) {
              @for (module of region.value.modules; track module.instanceId) {
                <div class="module-instance">{{ module.moduleId }}</div>
              }
            } @else {
              <div class="region-empty">
                <p>Drop modules here</p>
              </div>
            }
          </div>
        </div>
      }
    </div>
  `,
  styles: [`
    /* Template-specific styles */
  `],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class {Name}RendererComponent {
  readonly regions = input.required<Record<string, TemplateRegion>>();
  readonly config = input<TemplateConfig>();
  
  readonly regionClicked = output<string>();
  readonly moduleDropped = output<{ regionName: string; moduleId: string }>();

  readonly containerStyles = computed(() => {
    // Return CSS Grid styles based on config
  });
}

✅ Completed Template: Split Sidebar

Location: modules/canvas-templates/split-sidebar
Module ID: template-split-sidebar
Status: ✅ Complete

Features:


📋 Remaining Templates to Create

1. Two-Column Equal (template-two-column-equal)

2. Three-Column Equal (template-three-column-equal)

3. Header/Content/Footer (template-header-content-footer)

4. Dashboard (template-dashboard)

5. Custom (template-custom)


🔧 Quick Creation Checklist

For each new template:


🚀 Benefits of This Pattern

1. True Meta-Modularity 🧬

2. AI-Generatable 🤖

const aiPrompt = "Create a blog layout template";
const module = await ai.generateModule(aiPrompt);
registry.registerModule(module); // Instantly available!

3. Marketplace-Ready 🏪

const exported = template.publicActions.exportJSON();
await marketplace.publish(exported);

4. Platform-Agnostic 🌍

// Same module works on:
- Web (CSS Grid)
- Flutter (Row/Column/Expanded)
- React Native (flexbox)

5. Discoverable 🔍

const templates = registry.getModulesByCategory('layout-template');
const responsive = templates.filter(t => t.metadata.responsive);

📊 Progress Tracking

Template Status Module ID Flutter Mapped
Split Sidebar ✅ Complete template-split-sidebar ✅ Yes
Two-Column Equal ⏳ Next template-two-column-equal Pending
Three-Column Equal ⏳ Pending template-three-column-equal Pending
Header/Content/Footer ⏳ Pending template-header-content-footer Pending
Dashboard ⏳ Pending template-dashboard Pending
Custom ⏳ Pending template-custom Pending

🎯 Next Steps

  1. ✅ Complete remaining 5 template modules
  2. ⏳ Create template registry service
  3. ⏳ Update canvas-builder to query templates from registry
  4. ⏳ Update layout-selector to show all registered templates
  5. ⏳ Implement region module containers
  6. ⏳ Enable drag-drop modules into regions
  7. ⏳ Add AI template generation
  8. ⏳ Build marketplace export/import

Last Updated: November 23, 2025 - 3:00 PM
Session ID: 11.25.1.1
Status: 🎯 Active Development - Template Module Pattern Established
Achievement: First template module complete, pattern documented, 5 more to build