skills/angular-clean-architecture/SKILL.md
Scaffolds and extends Angular 18+ standalone features using Clean Architecture with DDD layering (presentation/application/domain/infrastructure), custom signal-based stores, facade pattern, and ports/adapters dependency inversion. Use when creating new Angular features/domains, adding use cases/facades/stores/ports/adapters, refactoring legacy NgModule/NgRx code toward clean architecture, or working with cross-domain communication via context registry.
npx skillsauth add fmflurry/settings-opencode angular-clean-architectureInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Before writing code, verify these anchors exist in the active branch:
| Anchor | What to look for |
|--------|-----------------|
| Reference clean module | At least one domain following the full clean architecture structure (application/domain/infrastructure/presentation) |
| Base store | A BaseStore class with signal-based state management and ResourceState<T> |
| Store loading operator | An RxJS operator (e.g., handleStoreLoading) bridging Observables to store updates |
| Context registry | A registry mapping cross-domain providers (e.g., contextProvidersFor()) |
If one or more anchors are missing:
any — use unknown if the type is truly unknownUseCase classes directly into components — always go through a Facadeinject() for all dependencies, never constructor paramssrc/app/<feature-area>/
├── <domain>/
│ ├── application/ # Application layer (orchestration)
│ │ ├── facades/ # Public API for components
│ │ ├── use-cases/ # Single-responsibility business operations
│ │ └── store/ # Signal-based state management
│ │
│ ├── domain/ # Domain layer (pure business logic, ZERO infra deps)
│ │ ├── models/ # Immutable TypeScript types
│ │ ├── ports/ # Abstract classes (dependency inversion)
│ │ ├── rules/ # Pure validation functions & constants
│ │ └── mappers/ # Pure data transformation functions
│ │
│ ├── infrastructure/ # Infrastructure layer (external concerns)
│ │ ├── adapter/ # Port implementations
│ │ ├── api/
│ │ │ ├── endpoints/ # HTTP client wrappers
│ │ │ ├── request/ # API request DTOs
│ │ │ └── response/ # API response DTOs
│ │ └── <domain>-infrastructure.providers.ts
│ │
│ ├── presentation/ # Presentation layer (UI)
│ │ ├── list/ # Feature pages
│ │ ├── details/
│ │ ├── create/
│ │ ├── edit/
│ │ └── forms/ # Reusable form components
│ │
│ ├── routes.ts # Lazy-loaded route definitions
│ ├── routes.constants.ts # Route path constants
│ ├── <domain>-service.providers.ts # All DI bindings for this domain
│ └── public-api.ts # Barrel exports for cross-domain use
Component --> Facade --> UseCase --> [Port] <-- Adapter --> Endpoint --> HttpClient
| | | ^ |
Presentation Application Application Domain Infrastructure
The store uses a custom BaseStore with signal-based state and ResourceState<T> wrapping.
For full store details: See store-system.md — covers ResourceState<T>, BaseStore API, KeyedResourceData, handleStoreLoading/handleKeyedStoreLoading operators, @AutoStartLoading and @AppCache decorators.
Condensed patterns for each layer. For full templates with code: See layer-templates.md.
| Layer | Key Conventions |
|-------|----------------|
| Domain Model | Use type (not interface/class), optional ?: props, group by entity |
| Domain Port | abstract class with abstract methods returning Observable<T>, Port suffix, one per operation (ISP) |
| Domain Rules | Pure functions, as const constants, zero framework deps |
| Domain Mappers | Pure mapXToY functions, Partial<T> returns, null-safe |
| Use Case | @Injectable() (no providedIn), inject ports via inject(), single responsibility |
| Facade | @Injectable() (no providedIn), inject store + use cases, expose signals via getters, @AppCache + @AutoStartLoading + handleStoreLoading for actions |
| Adapter | implements port, inject endpoint, transform DTOs to domain models |
| Endpoint | HttpClient + UrlBuilder + PaginatedRequestBuilder |
| Infra Providers | Function returning Provider[], bind ports to adapters |
| Service Providers | Aggregates facades + use cases + infra + contextProvidersFor() |
| Routes | loadComponent lazy loading, route-level providers, functional guards |
| Public API | Barrel exports: models, ports, adapters, store, provider functions |
| Component | Standalone, OnPush, inject() only, facade-only injection, signal()/computed()/effect(), input()/output() signals, SCSS, project selector prefix |
Uses a ContextRegistry with contextProvidersFor(). For full patterns: See cross-domain.md.
For full testing patterns by layer: See testing-patterns.md.
| Test Target | Mock | Verify | |-------------|------|--------| | Facade | Store + use cases | Orchestration logic | | Component | Facade only | Rendering + event delegation | | Use case | Ports | Delegation + business logic | | Adapter | Endpoints | DTO-to-model mapping |
Target 80%+ coverage.
ALWAYS update tests when refactoring code. This is non-negotiable.
When you modify source code:
getFormattedX() → readonly formattedX = computed(...)YOU MUST:
.spec.ts files that test the modified codenpx vitest run <path>.spec.tsCommon Angular Pattern - Method to Signal:
// Before
protected getFormattedDate(): string { return this.data().date; }
// Template: {{ getFormattedDate() }}
// Test: component.getFormattedDate()
// After
readonly formattedDate = computed(() => this.data().date);
// Template: {{ formattedDate() }}
// Test: component.formattedDate()
NEVER:
| Artifact | Pattern | Example |
|----------|---------|---------|
| Store class | <Domain>Store | CustomersStore |
| Store enum | <Domain>StoreEnum | CustomersStoreEnum |
| Store state type | <Domain>State | CustomersState |
| Facade | <Domain>Facade | CustomersFacade |
| Use case | <VerbNoun>UseCase | GetCustomersUseCase |
| Port (abstract) | <VerbNoun>Port | GetCustomersPort |
| Adapter | <VerbNoun>Adapter | GetCustomersAdapter |
| Endpoint | <Domain>Endpoint | CustomersEndpoint |
| Component | <prefix>-<feature>-<name> | app-customers-list |
| Service providers fn | <domain>ServicesProviders() | customersServicesProviders() |
| Infra providers fn | <domain>InfrastructureProviders() | customersInfrastructureProviders() |
| Context providers | <DOMAIN>_CONTEXT_PROVIDERS | CUSTOMERS_CONTEXT_PROVIDERS |
| Route constants | routes.constants.ts | N/A |
| Public API | public-api.ts | N/A |
| Spec files | <name>.spec.ts | customers.facade.spec.ts |
| Model types | <Entity> (PascalCase type) | Customer, CustomerFilters |
| Business rules | <domain>-<concern>.rule.ts | customer-fields.rule.ts |
| Mappers | <source>-mapper.ts | enterprise-mapper.ts |
Follow these steps in order. Full templates for each step in layer-templates.md.
domain/models/domain/ports/ — abstract class, Observable<T> returnsdomain/rules/ (if needed)infrastructure/adapter/infrastructure/api/endpoints/application/use-cases/application/store/ — extend BaseStoreapplication/facades/ — wire store + use casesFor full migration guide: See migration-guide.md.
Summary: Analyze current module → Introduce facade boundary → Extract use cases/ports → Isolate infrastructure → Align store to BaseStore → Replace direct domain coupling with context registry → Validate.
| Legacy Pattern | New Pattern |
|---------------|-------------|
| NgRx actions/effects/reducers | Custom BaseStore + handleStoreLoading |
| StoreModule.forFeature() | BaseStore with providedIn: 'root' |
| Direct Store.dispatch() in components | Facade methods |
| Direct Store.select() in components | Facade getter returning store signal |
| Services with BehaviorSubject state | BaseStore with ResourceState |
| Constructor injection | inject() function |
| NgModules | Standalone components + route providers |
| @Input() / @Output() decorators | input() / output() signal functions |
type (not interface/class)abstract class with Observable returnsHttpClient + UrlBuilderBaseStore with enum + state type@AppCache + @AutoStartLoading + handleStoreLoadingany type used anywhereinject() onlyany introduced anywhereWhen completing work on this codebase, include in your final message:
development
Use ONLY when the user explicitly asks to create/use a worktree, isolated worktree, parallel feature branch, or avoid branch collision; immediately create an isolated OpenCode worktree for that task. Do not trigger for ordinary feature work.
development
Pre-merge code review for Angular + TypeScript pull requests. Diffs current branch against a target branch, applies Angular-specific checklists (signals, RxJS, clean architecture, flurryx, TS strict), runs lint + tsc, and emits a tiered report (verbose for juniors, terse for seniors). Auto-loads project AGENTS.md rules. Use when user runs /cop-review, says "pre-merge review", "review before merging", "check my PR against <branch>", or invokes the merge-cop agent.
testing
Use this skill for any git work such as creating branches, staging changes, writing commit messages, pushing branches, or preparing pull requests. Delegates git execution to the git-specialist agent.
development
Signal-first reactive state management for Angular. Bridge RxJS streams into cache-aware stores, keyed resources, mirrored state, and replayable history. Use when generating or modifying Angular code that uses flurryx for state management, or when scaffolding new feature modules that follow the flurryx facade pattern.