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

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

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

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

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

  1. Always use standalone components - Required for dynamic component creation
  2. Implement OnPush strategy - Optimize performance
  3. Use Signals for state - Leverage Angular 20 reactivity
  4. Provide comprehensive inputs - Make components configurable
  5. Emit meaningful events - Enable parent components to react
  6. Handle errors gracefully - Display user-friendly error messages
  7. Support theming - Use CSS custom properties for styling

Public Actions

  1. Use descriptive IDs - Follow convention: module:action-name
  2. Define clear schemas - Document input and output structure
  3. Return structured results - Always include success/error information
  4. Log operations - Use LoggerService for debugging
  5. Emit events - Notify the system of important actions
  6. Handle errors - Never throw, always return error in result
  7. 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

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

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

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

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

  1. Separation of Concerns: tap() for side-effects, map() for transformations
  2. Composability: Easy to add operators like retry(), catchError(), timeout()
  3. Testability: Observables are easier to mock and test
  4. Consistency: Standard Angular pattern for async operations
  5. 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

Potential Public Actions

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.