EMBEDDABLES AND ACTIONS
Ecosistema completo para organización y gestión de contenido con interfaces espaciales
User Management - Embeddable Components & Public Actions
Overview
This document describes the embeddable components and public actions implemented in the user-management module for proof of concept and integration testing.
Embeddable Components
UserListWidgetComponent
A compact, reusable widget that displays a list of users with filtering capabilities. This component can be embedded in other modules' dashboards or pages.
Component ID
users:list-widget
Category
widget
Permissions Required
users:read
Inputs
| Input | Type | Required | Default | Description |
|---|---|---|---|---|
limit |
number |
No | 10 |
Maximum number of users to display |
roleFilter |
string |
No | undefined |
Filter users by role (e.g., 'admin', 'user') |
showAvatar |
boolean |
No | true |
Whether to show user avatars |
showStatus |
boolean |
No | true |
Whether to show user status badges |
Outputs
| Output | Type | Description |
|---|---|---|
userSelected |
User |
Emitted when a user is clicked |
Usage Example
import { UserListWidgetComponent } from '@libs-core/user-management';
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [UserListWidgetComponent],
template: `
<lib-user-list-widget
[limit]="5"
[roleFilter]="'admin'"
[showAvatar]="true"
[showStatus]="true"
(userSelected)="handleUserSelected($event)"
/>
`
})
export class DashboardComponent {
handleUserSelected(user: User): void {
console.log('User selected:', user);
// Navigate to user detail or perform action
}
}
Embedding via Module Manifest
// Other modules can discover and embed this component
const manifest = moduleRegistry.getManifest('user-management');
const widget = manifest.embeddableComponents?.find(
c => c.id === 'users:list-widget'
);
if (widget) {
// Dynamically create and embed the component
const componentRef = await createComponent(widget.componentType, {
environmentInjector: injector,
hostElement: containerElement,
});
// Set inputs
componentRef.setInput('limit', 10);
componentRef.setInput('roleFilter', 'admin');
// Subscribe to outputs
componentRef.instance.userSelected.subscribe((user) => {
console.log('User selected:', user);
});
}
Features
- Reactive State: Uses Angular Signals for efficient change detection
- OnPush Strategy: Optimized performance with ChangeDetectionStrategy.OnPush
- Async Loading: Displays loading state while fetching users
- Error Handling: Gracefully handles API errors with user-friendly messages
- Role Filtering: Client-side filtering by role
- Responsive Design: Adapts to different container sizes
- Accessible: Proper ARIA labels and keyboard navigation
Styling
The component uses CSS custom properties for theming:
--surface-color: Background color
--border-color: Border and divider colors
--text-primary: Primary text color
--text-secondary: Secondary text color
--primary-color: Brand color for accents
--error-color: Error state color
--success-light/dark: Success state colors
--grey-light/dark: Inactive state colors
--warning-light/dark: Warning state colors
--info-light/dark: Info state colors
Public Actions
NavigateToUserAction
A public action that enables other modules to navigate to a specific user's detail or edit page.
Action ID
users:navigate-to-user
Category
navigation
Permissions Required
users:read
Input Schema
{
userId: 'string', // Required: User ID to navigate to
mode: 'string' // Optional: 'detail' | 'edit' (defaults to 'detail')
}
Output Schema
{
success: 'boolean', // Whether navigation was successful
targetRoute: 'string', // The route that was navigated to
error: 'string' // Error message if navigation failed (optional)
}
Usage Example
import { inject } from '@angular/core';
import { NavigateToUserAction } from '@libs-core/user-management';
@Component({
selector: 'app-some-module',
standalone: true,
template: `
<button (click)="viewUser('usr_123')">View User</button>
<button (click)="editUser('usr_123')">Edit User</button>
`
})
export class SomeModuleComponent {
private readonly navigateToUser = inject(NavigateToUserAction);
async viewUser(userId: string): Promise<void> {
const result = await this.navigateToUser.execute({
userId,
mode: 'detail'
});
if (result.success) {
console.log('Navigated to:', result.targetRoute);
} else {
console.error('Navigation failed:', result.error);
}
}
async editUser(userId: string): Promise<void> {
const result = await this.navigateToUser.execute({
userId,
mode: 'edit'
});
if (result.success) {
console.log('Navigated to:', result.targetRoute);
} else {
console.error('Navigation failed:', result.error);
}
}
}
Execution via Action Registry
import { inject } from '@angular/core';
import { ActionRegistryService } from '@libs-core/action-registry';
@Component({
selector: 'app-dashboard',
standalone: true,
})
export class DashboardComponent {
private readonly actionRegistry = inject(ActionRegistryService);
async navigateToUser(userId: string): Promise<void> {
// Actions are registered by module-integrator during module activation
const result = await this.actionRegistry.execute('users:navigate-to-user', {
userId,
mode: 'detail'
});
console.log('Navigation result:', result);
}
}
Features
- Flexible Navigation: Supports both detail and edit modes
- Event Emission: Emits
users:navigatedevent for tracking - Comprehensive Logging: Logs all navigation attempts and results
- Error Handling: Returns structured error information
- Type Safety: Fully typed input and output interfaces
Events Emitted
When navigation is successful, the action emits a system event:
{
type: 'users:navigated',
payload: {
userId: string,
mode: 'detail' | 'edit',
targetRoute: string
},
timestamp: Date
}
Integration Testing
Testing Embeddable Components
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UserListWidgetComponent } from '@libs-core/user-management';
describe('UserListWidget Integration', () => {
let fixture: ComponentFixture<UserListWidgetComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserListWidgetComponent],
providers: [
// Mock services as needed
]
}).compileComponents();
fixture = TestBed.createComponent(UserListWidgetComponent);
});
it('should embed successfully in other modules', () => {
expect(fixture.componentInstance).toBeTruthy();
});
it('should accept input configuration', () => {
fixture.componentRef.setInput('limit', 5);
fixture.componentRef.setInput('roleFilter', 'admin');
expect(fixture.componentInstance.limit()).toBe(5);
expect(fixture.componentInstance.roleFilter()).toBe('admin');
});
});
Testing Public Actions
import { TestBed } from '@angular/core/testing';
import { Router } from '@angular/router';
import { NavigateToUserAction } from '@libs-core/user-management';
describe('NavigateToUserAction Integration', () => {
let action: NavigateToUserAction;
let router: Router;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
NavigateToUserAction,
// Router and other dependencies
]
});
action = TestBed.inject(NavigateToUserAction);
router = TestBed.inject(Router);
});
it('should execute navigation successfully', async () => {
const result = await action.execute({
userId: 'usr_123',
mode: 'detail'
});
expect(result.success).toBe(true);
expect(result.targetRoute).toBe('/users/usr_123');
});
});
Module Manifest Declaration
Both the embeddable component and public action are declared in the module manifest:
// libs/core/user-management/src/lib/user-management.module.ts
readonly manifest: ModuleManifest = {
id: 'user-management',
version: '1.0.0',
name: 'User Management',
embeddableComponents: [
{
id: 'users:list-widget',
componentType: UserListWidgetComponent,
displayName: 'User List Widget',
description: 'Compact widget displaying a list of users with filtering capabilities',
category: 'widget',
permissions: ['users:read'],
inputs: [
{
name: 'limit',
type: 'number',
required: false,
defaultValue: 10,
description: 'Maximum number of users to display',
},
{
name: 'roleFilter',
type: 'string',
required: false,
description: 'Filter users by role',
},
{
name: 'showAvatar',
type: 'boolean',
required: false,
defaultValue: true,
description: 'Whether to show user avatars',
},
{
name: 'showStatus',
type: 'boolean',
required: false,
defaultValue: true,
description: 'Whether to show user status badges',
},
],
},
],
publicActions: [
{
id: 'users:navigate-to-user',
displayName: 'Navigate to User',
description: 'Navigate to a specific user detail or edit page',
category: 'navigation',
permissions: ['users:read'],
inputSchema: {
userId: 'string',
mode: 'string',
},
outputSchema: {
success: 'boolean',
targetRoute: 'string',
error: 'string',
},
},
],
// ... other manifest properties
};
Best Practices
Embeddable Components
- Always use standalone components - Required for dynamic component creation
- Implement OnPush strategy - Optimize performance
- Use Signals for state - Leverage Angular 20 reactivity
- Provide comprehensive inputs - Make components configurable
- Emit meaningful events - Enable parent components to react
- Handle errors gracefully - Display user-friendly error messages
- Support theming - Use CSS custom properties for styling
Public Actions
- Use descriptive IDs - Follow convention:
module:action-name - Define clear schemas - Document input and output structure
- Return structured results - Always include success/error information
- Log operations - Use LoggerService for debugging
- Emit events - Notify the system of important actions
- Handle errors - Never throw, always return error in result
- Type everything - Use TypeScript interfaces for parameters and results
CRUD Public Actions
The user-management module now includes complete CRUD operations as public actions, allowing other modules to manage users programmatically.
CreateUserAction
Creates a new user in the system with role assignment.
Action ID
users:create-user
Category
crud
Permissions Required
users:create
Input Schema
{
email: string; // Required: User's email address
firstName: string; // Required: User's first name
lastName: string; // Required: User's last name
roleId: string; // Required: Role ID to assign
phone?: string; // Optional: Phone number
department?: string; // Optional: Department
tenantId: string; // Required: Tenant context
}
Output Schema
{
success: boolean; // Whether creation was successful
userId?: string; // Created user ID (if successful)
error?: string; // Error message (if failed)
}
Usage Example
import { inject } from '@angular/core';
import { ActionRegistryService } from '@libs-core/action-registry';
const actionRegistry = inject(ActionRegistryService);
const result = await actionRegistry.execute('users:create-user', {
email: '[email protected]',
firstName: 'John',
lastName: 'Doe',
roleId: 'role_123',
tenantId: 'tenant_abc'
});
if (result.success) {
console.log('User created:', result.userId);
} else {
console.error('Creation failed:', result.error);
}
UpdateUserAction
Updates an existing user's information.
Action ID
users:update-user
Category
crud
Permissions Required
users:update
Input Schema
{
userId: string; // Required: User ID to update
email?: string; // Optional: New email
firstName?: string; // Optional: New first name
lastName?: string; // Optional: New last name
roleId?: string; // Optional: New role ID
phone?: string; // Optional: New phone
department?: string; // Optional: New department
isActive?: boolean; // Optional: Active status
}
Output Schema
{
success: boolean; // Whether update was successful
userId?: string; // Updated user ID
updatedFields?: string[]; // List of fields that were updated
error?: string; // Error message (if failed)
}
Usage Example
const result = await actionRegistry.execute('users:update-user', {
userId: 'usr_123',
firstName: 'Jane',
lastName: 'Smith',
isActive: true
});
if (result.success) {
console.log('Updated fields:', result.updatedFields);
}
DeleteUserAction
Deletes a user from the system (supports soft and hard delete).
Action ID
users:delete-user
Category
crud
Permissions Required
users:delete
Input Schema
{
userId: string; // Required: User ID to delete
hardDelete?: boolean; // Optional: Permanent deletion (default: false)
reason?: string; // Optional: Deletion reason for audit
}
Output Schema
{
success: boolean; // Whether deletion was successful
userId?: string; // Deleted user ID
deletionType?: 'soft' | 'hard'; // Type of deletion performed
error?: string; // Error message (if failed)
}
Usage Example
// Soft delete (deactivation)
const softResult = await actionRegistry.execute('users:delete-user', {
userId: 'usr_123',
hardDelete: false,
reason: 'User requested account closure'
});
// Hard delete (permanent removal)
const hardResult = await actionRegistry.execute('users:delete-user', {
userId: 'usr_456',
hardDelete: true,
reason: 'Compliance requirement'
});
BulkUpdateRolesAction
Updates role assignments for multiple users in a single batch operation.
Action ID
users:bulk-update-roles
Category
workflow
Permissions Required
users:updateroles:assign
Input Schema
{
assignments: Array<{
userId: string; // User ID
roleId: string; // Role ID to assign
}>;
continueOnError?: boolean; // Continue processing if errors occur (default: true)
}
Output Schema
{
success: boolean; // Overall operation success
total: number; // Total assignments processed
successful: number; // Number of successful updates
failed: number; // Number of failed updates
results: Array<{
userId: string;
success: boolean;
error?: string;
}>;
error?: string; // Overall error (if operation failed completely)
}
Usage Example
const result = await actionRegistry.execute('users:bulk-update-roles', {
assignments: [
{ userId: 'usr_1', roleId: 'role_admin' },
{ userId: 'usr_2', roleId: 'role_user' },
{ userId: 'usr_3', roleId: 'role_manager' }
],
continueOnError: true
});
console.log(`Processed: ${result.total}, Success: ${result.successful}, Failed: ${result.failed}`);
Architecture: RxJS-First Implementation
All CRUD actions are implemented using RxJS Observables for reactive composition:
// Example pattern used in all actions
return await lastValueFrom(
this.userService.createUser(request).pipe(
tap(response => {
// Side effects: logging, events
this.logger.info('User created', { userId: response.data.id });
}),
map(response => {
// Transform to action output format
return {
success: response.success,
userId: response.data?.id,
error: response.message
};
})
)
);
Benefits of RxJS Pattern
- Separation of Concerns:
tap()for side-effects,map()for transformations - Composability: Easy to add operators like
retry(),catchError(),timeout() - Testability: Observables are easier to mock and test
- Consistency: Standard Angular pattern for async operations
- Cancelation: Built-in support for canceling operations
HTTP Integration
Actions use UserService which makes HTTP calls intercepted by the mock system:
Action → UserService (RxJS) → HTTP Client → Interceptor
↓
Dev/Test: user-mock-api.ts
Production: Real Backend
UserService API
@Injectable({ providedIn: 'root' })
export class UserService {
getUsers(params?): Observable<UserListResponse>
getUserById(userId): Observable<UserResponse>
createUser(request): Observable<UserResponse>
updateUser(userId, request): Observable<UserResponse>
deleteUser(userId): Observable<void>
changeUserStatus(userId, request): Observable<UserResponse>
bulkOperation(request): Observable<BulkOperationResult>
}
Future Enhancements
Potential Embeddable Components
UserFormWidget- Quick user creation formUserStatsWidget- User statistics dashboardUserRoleManager- Role assignment interface
Potential Public Actions
users:export-users- Integration action for data exportusers:import-users- Bulk user import from CSV/JSONusers:send-notification- Send notifications to usersusers:reset-password- Password reset workflow
Conclusion
The embeddable components and public actions provide a foundation for inter-module integration in the GoGrab Space platform. They demonstrate the modular architecture's capability to create reusable, composable components that can be discovered and utilized across different modules while maintaining proper encapsulation and type safety.