Angular Clean Architecture — Module System

SkillDev tools

Scaffolds and extends Angular standalone feature MODULES under src/app/modules/{name} using Clean Architecture layering (presentation/application/core/infrastructure), a self-registering module providers function, route-level lazy loading, cross-navigation state caching, the facade pattern, and ports/adapters dependency inversion. Use when creating a new Angular module, adding use cases/facades/stores/ports/adapters, wiring lazy routes + cached state, replacing session proxies, refactoring legacy NgModule/NgRx code, or cross-module communication via the context registry. Domain-modeling rules: see angular-ddd. State/replay mechanics: see flurryx.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Angular Clean Architecture — Module System skill

What this skill tells your AI

The instructions your AI receives, as published by fmflurry/settings-opencode in skills/angular-clean-architecture/SKILL.md and read by ahel’s review.

When To Activate

  • Creating a module under src/app/modules/
  • Adding a use case, facade, store, port, or adapter to an existing module
  • Wiring lazy routes + cached state
  • Replacing session proxies with self-contained module adapters
  • Creating standalone components that consume store state via facades
  • Refactoring a legacy/mixed module (NgModule/NgRx) toward Clean Architecture
  • Moving business logic out of components into facades/use-cases
  • Adding or updating cross-module communication via the context registry

Relationship to Other Skills

ConcernOwner
Domain modeling (entity, VO, aggregate, invariants)[[angular-ddd]]
State/replay mechanics (Store API, channels, history)[[flurryx]]
Pre-merge enforcement / review checklist[[angular-cop]]
Module layout, DI, ports/adapters, lazy loading, caching wiring, facade, context registry, session-proxy replacement, namingthis skill

Architecture Anchors (Verify Before Coding)

AnchorWhat to look for
Reference moduleAt least one module under src/app/modules/ with full layer structure
flurryx storeStore.for<Config>().build() in application/store/
syncToStore usagesyncToStore(this.store, KEY) or syncToKeyedStore(...) in adapters/facades
Context registrycontextProvidersFor() mapping cross-module providers

If anchors are missing: do NOT invent imports. Continue best-effort, report mismatch.

Architecture Overview

src/app/modules/{moduleName}/
├── presentation/            # list/ details/ create/ edit/ container pages, forms/, components/
├── application/
│   ├── facades/{moduleName}.facade.ts
│   ├── use-cases/{verb-noun}.use-case.ts
│   └── store/{moduleName}.store.ts      # flurryx Store.for<Config>().build()
├── core/                    # domain hexagon — ZERO infra/framework imports
│   ├── models/  ports/  rules/  mappers/  events/
├── infrastructure/
│   ├── adapters/{verb-noun}.adapter.ts
│   └── api/{endpoints/, request/, response/}
├── routes.ts                # lazy loadComponent + route-level providers
├── routes.constants.ts
├── {moduleName}-service.providers.ts    # SELF-REGISTRATION entrypoint
├── public-api.ts            # SYNC contract: models, ports, providers fn
└── integration-api.ts       # REACTIVE contract: exports store for cross-module mirroring

Full layout with worked example: module-template.md.

Dependency Rules (CRITICAL)

Component --> Facade --> UseCase --> [Port] <-- Adapter --> Endpoint --> HttpClient
    |            |           |          ^           |
Presentation  Application  Application   Core     Infrastructure
  • Core has ZERO infrastructure or framework dependencies — no Angular, no HttpClient, no flurryx. Only models (types), ports (abstract classes), rules (pure functions), mappers.
  • Application depends on Core only — facades orchestrate use cases + store; use cases call ports.
  • Infrastructure depends on Core only — adapters implement ports using HTTP clients.
  • Components NEVER inject use cases or stores directly — always inject facades.
  • Cross-module communication uses the Context Registry or integration-api store exports — never import another module's internals.

Root Store vs Route Providers (Caching Mechanism)

  • Store is providedIn: 'root' (flurryx default) → survives navigation = the cache.
  • Facades, use cases, adapters are route-scoped → disposed on leave.
  • @SkipIfCached prevents re-fetch when returning to a cached route.
  • Optional sessionStorage/localStorage channels for reload/tab survival.
  • clearAllStores() on logout/tenant switch.

Store System

flurryx provides event-sourced, replayable signal state. This skill does NOT restate its API.

  • API reference (Store builder, syncToStore, decorators, channels, history): [[flurryx]]
  • Wiring cheat-sheet (how stores plug into modules): store-system.md
  • Conceptual ES/CQRS → Angular mapping: [[angular-ddd]] event-sourcing-mapping

Session-Proxy Replacement

Legacy *Proxy classes + shared god-stores are replaced by self-contained module adapters with @SkipIfCached(CACHE_NO_TIMEOUT) + optional session-storage channel, consumed by PORT via context registry.

Full before/after migration: session-proxy-migration.md.

Hard Rules

  • Never use any — use unknown if the type is truly unknown
  • Never inject UseCase classes directly into components — always go through a Facade
  • Never inject stores directly into components — facades expose store signals
  • Never import flurryx, Angular, or HttpClient in core/
  • Never re-fetch reference data without @SkipIfCached
  • Never inject a concrete adapter/proxy across modules — inject the port
  • Components must depend on facades for ALL domain interactions
  • Use inject() for all dependencies, never constructor params
  • Domain modeling patterns (entity, VO, aggregate) → [[angular-ddd]], not here

Layer Implementation Templates

For full templates with code: See layer-templates.md.

LayerKey Conventions
Core Modeltype or interface, optional ?: props, group by entity
Core Portabstract class, Observable<T> returns, Port suffix, one per operation (ISP)
Core RulesPure functions, as const constants, zero framework deps
Core MappersPure 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, @SkipIfCached + @Loading + syncToStore
StoreStore.for<Config>().build()providedIn: 'root' by default
Adapterimplements port, inject endpoint, DTO↔domain mapping (ACL)
EndpointHttpClient + UrlBuilder + PaginatedRequestBuilder
Infra ProvidersFunction returning Provider[], bind ports to adapters
Service ProvidersAggregates facades + use cases + infra + contextProvidersFor()
RoutesloadComponent lazy loading, route-level providers
Public APISYNC contract: models, ports, providers fn
Integration APIREACTIVE contract: store export for cross-module mirroring
ComponentStandalone, OnPush, inject() only, facade-only, signals

Cross-Module Communication

Two mechanisms: sync (context registry binding to ports) and reactive (integration-api store exports + flurryx mirroring). For full patterns: See cross-domain.md.

Testing Patterns

For full testing patterns by layer: See testing-patterns.md.

Test TargetMockVerify
FacadeStore + use casesOrchestration logic
ComponentFacade onlyRendering + event delegation
Use casePortsDelegation + business logic
AdapterEndpointsDTO-to-model mapping

Target 80%+ coverage.

Naming Conventions

ArtifactPatternExample
Store<Module>Store (const from Store.for)CompaniesStore
Store config<Module>StoreConfigCompaniesStoreConfig
Facade<Module>FacadeCompaniesFacade
Use case<VerbNoun>UseCaseGetCompaniesUseCase
Port (abstract)<VerbNoun>PortGetCompaniesPort
Adapter<VerbNoun>AdapterGetCompaniesAdapter
Endpoint<Module>EndpointCompaniesEndpoint
Component<prefix>-<module>-<name>app-companies-list
Service providers fn<module>ServicesProviders()companiesServicesProviders()
Infra providers fn<module>InfrastructureProviders()companiesInfrastructureProviders()
Context providers<MODULE>_CONTEXT_PROVIDERSCOMPANIES_CONTEXT_PROVIDERS
Public APIpublic-api.tsN/A
Integration APIintegration-api.tsN/A
Model types<Entity> (PascalCase)Company, CompanyFilters
Business rules<module>-<concern>.rule.tscompany-fields.rule.ts
Mappers<source>-mapper.tsenterprise-mapper.ts

Legacy folder mapping: domain/core/; adapter/adapters/; src/app/<area>/src/app/modules/{name}/.

Implementation Playbook (Add a New Module)

Follow these steps in order. Full templates in layer-templates.md and module-template.md.

  1. Create module folder src/app/modules/{moduleName}/
  2. Define core models in core/models/
  3. Define core ports in core/ports/abstract class, Observable<T> returns
  4. Add business rules in core/rules/ (if needed)
  5. Create API endpoint in infrastructure/api/endpoints/
  6. Implement adapter in infrastructure/adapters/ — implements port, maps DTOs
  7. Register infrastructure providers — bind ports to adapters
  8. Define store in application/store/Store.for<Config>().build()
  9. Create use case in application/use-cases/
  10. Create facade in application/facades/ — wire store + use cases + @SkipIfCached/@Loading/syncToStore
  11. Create service providers — aggregate all DI bindings + contextProvidersFor()
  12. Create routes with lazy-loaded components and route-level providers
  13. Create standalone components — facade-only injection, signals, OnPush
  14. Create public-api.ts (sync contract) and integration-api.ts (reactive contract)
  15. Register in app.routes.ts via loadChildren
  16. Write tests (see testing-patterns.md)
  17. Register context if cross-module access needed (see cross-domain.md)

Legacy Patterns (What NOT to Replicate)

Legacy PatternNew Pattern
NgRx actions/effects/reducersflurryx Store.for().build() + syncToStore
extends BaseStoreStore.for<Config>().build()
handleStoreLoading(store, key)syncToStore(store, key)
handleKeyedStoreLoading(store, key, id)syncToKeyedStore(store, key, id)
@AppCache(key, fn)@SkipIfCached(key, fn)
@AutoStartLoading(key, fn)@Loading(key, fn)
GetXProxy classModule adapter + @SkipIfCached(CACHE_NO_TIMEOUT)
Shared ReferenceSessionStorePer-module store + optional session-storage channel
src/app/<area>/ top-level featuresrc/app/modules/{name}/
StoreModule.forFeature()Store.for<Config>().build() (root-provided)
Direct Store.dispatch() in componentsFacade methods
Direct Store.select() in componentsFacade getter returning store signal
Services with BehaviorSubject stateflurryx store with ResourceState<T>
Constructor injectioninject() function
NgModulesStandalone components + route providers
@Input() / @Output() decoratorsinput() / output() signal functions

Checklist: Adding a New Module

  • Module folder created under src/app/modules/{name}/
  • Core models defined (type or interface, no any)
  • Ports defined as abstract class with Observable returns in core/ports/
  • Core has ZERO imports from Angular, HttpClient, or flurryx
  • Adapters implement ports, inject endpoints, map DTOs
  • Infrastructure providers bind ports to adapters
  • Store defined via Store.for<Config>().build() in application/store/
  • Use cases inject ports, single responsibility
  • Facade injects store + use cases, exposes signals
  • Facade uses @SkipIfCached + @Loading + syncToStore
  • Service providers aggregate all DI bindings
  • Routes lazy-load components with route-level providers
  • Components inject facades only, use signals + OnPush
  • public-api.ts exports models, ports, providers fn
  • integration-api.ts exports store for cross-module mirroring
  • Registered in app.routes.ts via loadChildren
  • Tests written (80%+ coverage)
  • No any type used anywhere
  • No constructor injection — inject() only
  • Context registry updated if cross-module access needed

Review Checklist (Before Finalizing)

  • Components use facade only — no use case or store references in presentation
  • No any introduced anywhere
  • Core layer has zero framework/infra imports
  • Store is root-provided (not in route providers)
  • @SkipIfCached outermost, @Loading beneath
  • Cross-module access via port (context registry) or store mirror (integration-api)
  • All event handlers are thin — logic delegated to facade
  • Immutable updates throughout — no object mutation

Signals

GitHub stars
171
Forks
10
Last commit
Aug 2026
Advanced
Catalog kind
skill
Gateway key
angular-clean-architecture
Source
github.com/fmflurry/settings-opencode