Embeddable Components Guide
Complete guide to creating and using embeddable widgets in modules
🧩 Embeddable Components Guide
Embeddable components are reusable UI widgets that modules expose for cross-module composition.
🎯 What Are Embeddables?
Embeddable components are standalone Angular components that:
- Can be embedded in other modules' dashboards or pages
- Have well-defined inputs and outputs
- Are discoverable through the module manifest
- Support dynamic instantiation
🏗️ Creating an Embeddable
1. Create the Component
import { Component, input, output, signal, ChangeDetectionStrategy } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'lib-user-list-widget',
standalone: true,
imports: [CommonModule],
template: `
<div class="widget-container">
<h3>{{ title() }}</h3>
@for (user of users(); track user.id) {
<div class="user-item" (click)="selectUser(user)">
{{ user.name }}
</div>
}
</div>
`,
styleUrl: './user-list-widget.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserListWidgetComponent {
// System inputs (required for widget integration)
readonly widgetId = input<string>('');
readonly widgetConfig = input<ResolvedWidgetConfig>();
// Component-specific inputs
readonly limit = input<number>(10);
readonly roleFilter = input<string | undefined>();
// Component outputs
readonly userSelected = output<User>();
// Internal state
readonly users = signal<User[]>([]);
selectUser(user: User): void {
this.userSelected.emit(user);
}
}
###2. Register in Module Manifest
@ModuleDecorator({
id: 'user-management',
// ...
})
export class UserManagementModule implements BrowserModuleContract {
readonly manifest: ModuleManifest = {
embeddableComponents: [
{
id: 'users:list-widget',
componentType: UserListWidgetComponent,
displayName: 'User List Widget',
description: 'Compact widget displaying users',
category: 'widget',
permissions: ['users:read'],
inputs: [
ParameterBuilders.number('limit')
.defaultValue(10)
.min(1)
.max(50)
.build(),
ActionParameterBuilder.string('roleFilter')
.description('Filter by role')
.build()
]
}
]
};
}
🔌 Using Embeddables
In Another Module
<!-- Static usage -->
<lib-user-list-widget
[limit]="5"
[roleFilter]="'admin'"
(userSelected)="handleUserSelected($event)"
/>
Dynamic Discovery
// Discover embeddable from module registry
const manifest = moduleRegistry.getManifest('user-management');
const widget = manifest.embeddableComponents?.find(c => c.id === 'users:list-widget');
// Create dynamically
const componentRef = await createComponent(widget!.componentType, {
environmentInjector: injector,
hostElement: container
});
// Set inputs
componentRef.setInput('limit', 10);
// Subscribe to outputs
componentRef.instance.userSelected.subscribe(user => {
console.log('User selected:', user);
});
✅ Best Practices
- Always Standalone: Embeddables MUST be standalone components.
- OnPush Strategy: Use
ChangeDetectionStrategy.OnPush. - Signal-Based State: Use signals for reactive state.
- Widget System Inputs: Include
widgetIdandwidgetConfiginputs. - Themed: Use CSS custom properties from the theme system.
- Error Handling: Handle errors gracefully, don't crash the host.
- Accessible: Proper ARIA labels and keyboard navigation.
##📚 Related Documentation
🧠 Property Metadata APIs (AI + Builder)
Embeddables share their dynamic inputs/outputs with the widget builder through the Properties backend. Two new endpoints keep AI generation aligned with reusable property templates:
GET /api/spaces/:spaceId/properties/templates/ai-context— compact payload the AI prompt builder consumes. Returns each template with its properties, categories, optionalthemeVariable, andaiMetadataimportance/use-cases.GET /api/spaces/:spaceId/properties/core-sync— full synchronization payload for the builder/registry caches. Includes everyPropertyDefinitionandPropertyTemplateplus counts and timestamps so Flash Persistence knows when to refresh.
Example core-sync response:
{
"success": true,
"data": {
"generatedAt": "2025-12-04T21:18:11.421Z",
"propertyCount": 58,
"templateCount": 9,
"properties": [
{
"key": "backgroundColor",
"category": "appearance",
"controlType": "color",
"themeVariable": "--ux-surface",
"aiMetadata": { "importance": "essential", "useCases": ["cards","buttons"] }
}
],
"templates": [
{
"id": "template_appearance",
"aiSummary": "Standard colors + typography",
"properties": [...]
}
]
}
}
Always prefer these endpoints over hard-coded property lists so embeddables remain future proof across modules.
Last Updated: December 3, 2025 Session ID: 3