Client Development
The Angular client provides a management UI for credential configuration, presentation management, monitoring, and administration. It follows a feature-based structure with standalone components and a clear separation of concerns.
Directory Map
apps/client/src/app/
├── app.component.ts # Root application component
├── app.config.ts # Application-level providers and configuration
├── app.routes.ts # Route definitions
├── core/ # Core services and global functionality
│ ├── api.service.ts # Backend API client wrapper
│ ├── oidc.service.ts # OIDC authentication service
│ └── auth.interceptor.ts # HTTP request authentication interceptor
├── services/ # Shared application services
│ ├── environment.service.ts
│ ├── grafana-link.service.ts
│ └── jwt.service.ts
├── guards/ # Route guards (authentication, authorization)
├── common/ # Shared utilities and base components
├── utils/ # Reusable UI components
│ ├── editor/ # JSON/code editor component
│ ├── image-field/ # Image upload/display component
│ └── webhook-config-*/ # Webhook configuration components
├── admin/ # Administrative features
├── tenants/ # Tenant management
├── users/ # User management
├── issuance/ # Credential issuance configuration
│ ├── credential-config/
│ ├── issuance-config/
│ ├── issuance-offer/
│ └── attribute-provider/
├── presentation/ # Presentation and verification configuration
│ └── presentation-config/
├── session-management/ # Active session monitoring
├── key-management/ # Key and KMS provider management
├── trust-list/ # Trust list configuration
├── status-list-*/ # Status list management
├── webhook-endpoint/ # Webhook endpoint management
├── registrar/ # Registrar configuration
└── schema-metadata/ # Schema metadata management
Architecture Patterns
Standalone Components
All components use the standalone component pattern introduced in Angular 15+. Components import their dependencies directly in the imports array:
@Component({
selector: 'app-tenant-create',
imports: [
ReactiveFormsModule,
MatCardModule,
MatFormFieldModule,
// ... other imports
],
templateUrl: './tenant-create.component.html',
styleUrl: './tenant-create.component.scss',
})
export class TenantCreateComponent { /* ... */ }
This eliminates the need for NgModule declarations in most cases.
Smart vs. Dumb Components
The codebase follows the smart/dumb component pattern:
- Smart components (container components): Orchestrate data and business logic, interact with services, manage state, and handle routing. Examples:
TenantListComponent,SessionManagementListComponent - Dumb components (presentational components): Only receive data via
@Input()and emit events via@Output(). They are pure UI components with no service dependencies. Examples: components inutils/folder
:::tip Component Responsibility When creating a new component, decide whether it should be smart (owns logic) or dumb (only displays data). Keep dumb components truly stateless and dependency-free. :::
Reactive Forms
All forms must use Reactive Forms — template-driven forms are not allowed. Forms are constructed using FormBuilder and FormGroup:
export class TenantCreateComponent {
tenantForm: FormGroup;
constructor(private readonly fb: FormBuilder) {
this.tenantForm = this.fb.group({
id: ['', [Validators.required]],
name: ['', [Validators.required]],
description: [''],
roles: new FormControl<Role[]>(['clients:manage'], [Validators.required]),
});
}
}
Generated API Client
The client uses a generated TypeScript API client from @eudiplo/sdk-core. Never make raw HTTP calls or hardcode API URLs:
import {
tenantControllerGetTenant,
tenantControllerInitTenant,
tenantControllerUpdateTenant,
} from '@eudiplo/sdk-core';
Regenerate the API client when backend endpoints change:
pnpm run gen:api
State Management
State is managed in services using RxJS patterns:
- Use
BehaviorSubjectto hold state - Expose state as
Observablefor consumption - Services in
core/andservices/manage shared state
Example pattern:
@Injectable({ providedIn: 'root' })
export class MyStateService {
private _data$ = new BehaviorSubject<DataType | null>(null);
readonly data$ = this._data$.asObservable();
updateData(data: DataType) {
this._data$.next(data);
}
}
Feature Organization
Features are organized by business capability, matching the backend structure:
- Issuance: Credential configuration, issuance configuration, attribute providers, and offer generation
- Presentation: Presentation configuration, verification rules, and credential requests
- Key Management: Key chains, KMS providers, and key rotation
- Trust: Trust list management and certificate validation
- Sessions: Active session monitoring and session configuration
- Admin: Users, tenants, clients, and audit logs
Each feature typically contains:
- List component (
*-list/): Displays items in a table or grid - Show component (
*-show/): Displays details of a single item - Create/Edit component (
*-create/): Form for creating or editing items
Development Workflow
Running the Client
Start the development server:
pnpm --filter @eudiplo/client start
# or from the repository root
pnpm run dev # starts all applications
The client runs on http://localhost:4200 by default.
Code Quality Checks
Before submitting changes:
# Format code
pnpm --filter @eudiplo/client run format
# Check formatting
pnpm --filter @eudiplo/client run format:check
# Run linting
pnpm --filter @eudiplo/client run lint
# Build the application
pnpm --filter @eudiplo/client run build
Testing
Run tests for the client:
pnpm --filter @eudiplo/client run test
Material Design Components
The client uses Angular Material for UI components. Common imports include:
MatCardModule,MatButtonModule,MatFormFieldModuleMatInputModule,MatSelectModule,MatTableModuleMatDialogModule,MatSnackBarfor notificationsMatIconModule,MatTooltipModule
Use Material components for consistency and accessibility.
Adding a New Feature
When adding a new feature:
- Create a feature folder under
apps/client/src/app/ - Create list, show, and create components as needed
- Define routes in the feature or in
app.routes.ts - Use the generated API client for backend communication
- Follow reactive forms for all input collection
- Separate smart (data-fetching) from dumb (UI-only) components
- Add appropriate guards for authentication/authorization
Related Documentation
- Development Setup — Environment configuration and running locally
- Repository Structure — Monorepo layout and workspace conventions
- Testing — Writing and running tests
- Backend Development — Understanding the API structure