Public Actions Guide

Complete guide to creating and using public actions

⚡ Public Actions Guide

Public Actions enable cross-module communication through a standardized, type-safe command interface.


🎯 What Are Public Actions?

Public actions are executable operations that:


🏗️ Creating a Public Action

1. Define Input/Output Interfaces

// Input extends ActionParams for compatibility
export interface CreateUserInput extends ActionParams {
  email: string;
  firstName: string;
  lastName: string;
  roleId: string;
}

// Output interface
export interface CreateUserOutput {
  success: boolean;
  userId?: string;
  error?: string;
}

2. Implement the Action

import { Injectable, inject } from '@angular/core';
import { Observable, of, switchMap, tap, catchError } from 'rxjs';
import {
  PublicAction,
  ExecutableAction,
  ActionContext,
  ActionParameterBuilder
} from '@libs-core/action-registry';
import { LoggerService } from '@libs-core/log-system';

@PublicAction({
  id: 'users:create-user',
  name: 'Create User',
  description: 'Creates a new user with role assignment',
  permissions: ['users:create'],
  tags: ['users', 'crud'],
  parameters: [
    ParameterBuilders.email('email').build(),
    ActionParameterBuilder.string('firstName').required().build(),
    ActionParameterBuilder.string('lastName').required().build()
  ]
})
@Injectable({ providedIn: 'root' })
export class CreateUserAction implements ExecutableAction<CreateUserOutput> {
  readonly metadata = {
    id: 'users:create-user',
    name: 'Create User',
    description: 'Creates a new user with role assignment',
    requiredPermissions: ['users:create'] as const,
    category: 'crud',
    enabled: true
  };
  
  private readonly logger = inject(LoggerService).child({ source: 'CreateUserAction' });
  private readonly userService = inject(UserService);
  
  execute(params: CreateUserInput, context: ActionContext): Observable<CreateUserOutput> {
    return this.validate(params).pipe(
      switchMap(isValid => {
        if (!isValid) {
          return of({ success: false, error: 'Invalid parameters' });
        }
        
        this.logger.info('Executing create user', {
          email: params.email,
          executionId: context.executionId
        });
        
        return this.userService.createUser(params).pipe(
          tap(() => this.logger.info('User created successfully')),
          map(user => ({ success: true, userId: user.id })),
          catchError(err => of({ success: false, error: err.message }))
        );
      })
    );
  }
  
  validate(params: CreateUserInput): Observable<boolean> {
    if (!params.email || !params.firstName || !params.lastName) {
      this.logger.error('Invalid input: missing required fields');
      return of(false);
    }
    return of(true);
  }
  
  cleanup(): void {
    this.logger.debug('Cleaning up CreateUserAction resources');
  }
}

3. Register in Module Manifest

readonly manifest: ModuleManifest = {
  id: 'user-management',
  // ...
  publicActions: [
    CreateUserAction,  // ✅ Array of action classes
    UpdateUserAction,
    DeleteUserAction
  ]
};

🔌 Using Public Actions

Direct Injection

@Component({...})
export class MyComponent {
  private readonly createUserAction = inject(CreateUserAction);
  
  async createUser(): Promise<void> {
    const result = await lastValueFrom(
      this.createUserAction.execute({
        email: '[email protected]',
        firstName: 'Test',
        lastName: 'User',
        roleId: 'role-123'
      }, context)
    );
    
    if (result.success) {
      console.log('User created:', result.userId);
    }
  }
}

Via ActionRegistry

@Component({...})
export class MyComponent {
  private readonly actionRegistry = inject(ActionRegistryService);
  
  async createUser(): Promise<void> {
    const result = await lastValueFrom(
      this.actionRegistry.execute('users:create-user', {
        email: '[email protected]',
        // ...
      })
    );
  }
}

✅ Best Practices

  1. Observable Returns: Always return Observable, never Promise.
  2. Validation Method: Implement validate() for parameter checking.
  3. Cleanup Method: Implement cleanup() for resource disposal.
  4. Structured Logging: Use LoggerService with context.
  5. Error Handling: Never throw - return error in result object.
  6. JSDoc: Document with comprehensive examples.

📚 Related Documentation


Last Updated: December 3, 2025 Session ID: 3