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:
- Implement the
ExecutableAction<T>interface - Are decorated with
@PublicAction - Can be called by other modules via ActionRegistry
- Have well-defined input/output contracts
- Support validation and permissions
🏗️ 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
- Observable Returns: Always return
Observable, neverPromise. - Validation Method: Implement
validate()for parameter checking. - Cleanup Method: Implement
cleanup()for resource disposal. - Structured Logging: Use
LoggerServicewith context. - Error Handling: Never throw - return error in result object.
- JSDoc: Document with comprehensive examples.
📚 Related Documentation
Last Updated: December 3, 2025 Session ID: 3