Org Skills

angular-architecture

Architect-level guidance for modern Angular (v17+) applications — standalone components, signals-based state, feature-sliced folder structure, dependency injection design, routing with lazy loading, RxJS boundaries, Nx monorepos, and migrating legacy NgModule/zone.js codebases.

Download .zip Raw Source
When agents use itUse this whenever the user is starting or restructuring an Angular app, choosing between NgRx / SignalStore / plain signal services, asking how to organise modules or libraries, deciding on Angular Material vs PrimeNG vs custom design system, planning a migration to standalone/signals/zoneless, or asks "how should I structure this" for anything Angular. Also apply it when reviewing Angular code for architectural problems.

Install

Copilot (VS Code, Visual Studio, Copilot CLI and github.com) reads skills from the repository — commit them so the whole team gets them.

npx skills add AGCO-Global/org-skills --skill angular-architecture -a github-copilot
# or with the org installer (adds .github/skills/angular-architecture):
npx -y github:AGCO-Global/org-skills add skill angular-architecture

Uses the open skills CLI. Works with Claude Code, Codex, Cursor, Copilot, Gemini CLI, OpenCode, Windsurf and 60+ others — it asks which agent to install into.

npx skills add AGCO-Global/org-skills --skill angular-architecture
# user-level instead of project-level:
npx skills add AGCO-Global/org-skills --skill angular-architecture -g

Installs the frontend-angular-skills plugin, which bundles all Frontend / Angular skills and keeps them updated.

/plugin marketplace add AGCO-Global/org-skills
/plugin install frontend-angular-skills@org-skills
# or just this skill, in this repository:
npx skills add AGCO-Global/org-skills --skill angular-architecture -a claude-code

Use Download .zip above, then upload it under Customize → Skills → Upload skill. Team and Enterprise admins can sync this repo as a plugin marketplace instead.

Installs into .agents/skills/, which Codex reads.

npx skills add AGCO-Global/org-skills --skill angular-architecture -a codex

Installs into Cursor's skills folder.

npx skills add AGCO-Global/org-skills --skill angular-architecture -a cursor

Installs into Gemini CLI's skills folder.

npx skills add AGCO-Global/org-skills --skill angular-architecture -a gemini-cli

Any tool with rules, instructions or custom prompts: use Copy SKILL.md above and paste it in. It is plain Markdown.

Commands use your normal git sign-in to GitHub, so they work while the repository is private. Node.js 20+ required.

Skill contents

Angular Architecture

Modern Angular is standalone components, signals, and functional DI. If the user's codebase is NgModules + heavy RxJS-in-templates + any, the architecture conversation is a migration conversation — say so and plan it.

1. Baseline decisions (record in ARCHITECTURE.md)

Decision Default Change when
Components Standalone, ChangeDetectionStrategy.OnPush, signals for inputs/outputs (input(), output(), model()) Never for new code.
Change detection Zoneless (provideZonelessChangeDetection()) on new apps; OnPush + signals everywhere Legacy libs depending on zone.js → keep zone until they're replaced; still OnPush.
State Signals in services for local/feature state; NgRx SignalStore for shared feature state; NgRx Store only for large event-sourced domains Small app → signals only.
Async RxJS at the edges (HTTP, WebSockets, router events, complex event streams), convert to signals with toSignal() for templates Never subscribe manually in components when toSignal/AsyncPipe works.
HTTP HttpClient with functional interceptors; HttpResource / httpResource() where the version supports it
Forms Typed Reactive Forms (or Signal Forms when stable) Template-driven forms only for trivial cases.
UI library Angular Material + CDK (tokens via M3 theming) or PrimeNG for data-heavy enterprise Existing design system → CDK primitives + custom components.
Tooling Angular CLI with esbuild/Vite builder (application builder), Nx when > 1 app or shared libs
Testing Vitest (via @angular/build:unit-test) or Jest, Testing Library, Playwright Karma is deprecated — migrate.
SSR @angular/ssr with hydration and incremental hydration for public/SEO apps Internal apps: CSR is fine.

2. Folder structure — feature-first with explicit public APIs

src/app/
├── app.config.ts          # providers: router, http, zoneless, i18n
├── app.routes.ts          # top-level lazy routes only
├── core/                  # singletons: auth, http interceptors, error handler, config, logger
│   ├── auth/
│   ├── http/
│   └── layout/            # shell, nav — one place
├── features/
│   └── orders/
│       ├── orders.routes.ts       # lazy-loaded route file
│       ├── pages/                 # routed components (smart)
│       ├── components/            # feature-private presentational
│       ├── data/                  # OrdersApi (HttpClient), OrdersStore (signals/SignalStore)
│       ├── model/                 # types, pure functions
│       └── index.ts               # public API
├── shared/
│   ├── ui/                # dumb reusable components, directives, pipes
│   ├── util/              # pure helpers
│   └── data-access/       # cross-feature API clients only if truly shared
└── entities/              # cross-feature domain models

Rules:

  • Features import from shared/, core/, entities/, and other features' index.ts only. Enforce with @nx/enforce-module-boundaries or eslint-plugin-boundaries.
  • core/ is provided once at root; nothing in core/ imports from features/.
  • Pages are the only components that inject stores/APIs; everything under components/ receives input() and emits output().
  • One component per file; Component suffix; .component.ts/.component.html/.component.css or inline template for small components.

In an Nx monorepo the same layers become library types: feature-*, ui-*, data-access-*, util-*, tagged and boundary-enforced.

3. Component design

  • Inputs/outputs as signals: readonly user = input.required<User>(), readonly save = output<User>(), two-way with model().
  • Derive with computed(), never store derived values in fields updated by effect().
  • effect() is for side effects only (logging, syncing to localStorage, imperative DOM). If you're calling .set() inside an effect, you almost certainly want computed() or linkedSignal().
  • Control flow: @if, @for (with mandatory track), @switch, @defer for below-the-fold heavy components.
  • Host bindings via host: {} in the decorator, not @HostBinding/@HostListener sprinkled through the class.
  • inject() in field initialisers, not constructor parameters. Enables composition into functions (injectAuthUser()).
  • Content projection with ng-content select and contentChild() signals for compound components (<app-card><app-card-header/>…).
  • Directives for cross-cutting behaviour (tooltip, autofocus, permission gating) instead of wrapping components.

4. State management — pick by scope

Scope Pattern
Component-local signal() / computed() in the component
Feature, shared by a few components Service with signals, providedIn: 'root' or provided at the route level for per-feature lifetime
Feature, with async loading, entities, and methods NgRx SignalStore (signalStore, withState, withComputed, withMethods, rxMethod, withEntities)
Server data cache httpResource() / resource() where available; otherwise a small query layer (TanStack Query for Angular is viable)
URL state Router params/query params, read via input() with withComponentInputBinding()
Forms Reactive Forms — the form is the state
App-wide, event-sourced, audited NgRx Store with actions/reducers/effects — justify it in writing

Rule of thumb: a store per feature, provided at the feature route, destroyed on leave. Root-provided global stores only for session/auth/preferences.

5. RxJS boundaries

RxJS is for streams of events over time; signals are for state right now. Keep them apart:

  • HTTP: HttpClient returns Observables — map into a signal with toSignal(obs$, { initialValue }) or feed a SignalStore rxMethod.
  • Complex user-event orchestration (typeahead with debounce/switchMap/cancellation) — RxJS pipeline, toSignal at the end.
  • Router, WebSocket, DOM event streams — RxJS.
  • Templates never contain | async chains on freshly created Observables; never subscribe() in components without takeUntilDestroyed(); never nested subscribes.
  • Prefer switchMap for cancellable requests, exhaustMap for submit buttons, mergeMap only when parallelism is intentional.

6. Dependency injection design

  • Functional providers (provideRouter, provideHttpClient(withInterceptors([...]))) in app.config.ts.
  • InjectionToken with providedIn: 'root' factories for configuration and abstraction points (API_BASE_URL, LOGGER).
  • Route-level providers for feature-scoped services (providers: [OrdersStore] in the route).
  • Abstract classes as tokens when there are multiple implementations (e.g. StorageService with LocalStorageService/MemoryStorageService for tests).
  • Functional guards, resolvers, and interceptors (CanActivateFn, ResolveFn, HttpInterceptorFn).

7. Routing

  • Every feature is a loadChildren lazy route pointing at <feature>.routes.ts.
  • withComponentInputBinding() so route params arrive as input() signals.
  • Guards for auth/permissions; resolvers only for data that must exist before render — otherwise render a skeleton and load in the component.
  • Preloading strategy: PreloadAllModules for small apps, custom (on hover/visible) for large.
  • Route-level title and data for breadcrumbs and analytics; typed via a shared RouteData interface.

8. Migration playbook (legacy → modern)

Sequence matters; each step ships independently:

  1. Upgrade to the latest LTS with ng update, one major at a time.
  2. ng generate @angular/core:standalone — three-step schematic (components → remove modules → bootstrap).
  3. Switch builder to @angular/build:application (esbuild).
  4. ng generate @angular/core:control-flow for @if/@for.
  5. ng generate @angular/core:signal-input-migration, output-migration, inject-migration.
  6. Set OnPush everywhere; fix what breaks (it reveals hidden mutation).
  7. Replace subject-based services with signals / SignalStore, feature by feature.
  8. Remove zone.js: provideZonelessChangeDetection(), remove zone.js polyfill, fix libs.
  9. Karma → Vitest/Jest.

Track each step in a checklist in the repo; measure bundle size and CD cycles before/after.

9. Non-negotiables checklist

  • All new components standalone + OnPush + signal inputs
  • @for always has track
  • No subscribe() without takeUntilDestroyed() or toSignal
  • Features lazy-loaded; boundaries enforced by lint
  • strict: true, strictTemplates: true, noImplicitOverride
  • Every route has a loading/skeleton state and an error path
  • ESLint with @angular-eslint (template a11y rules on) in CI
  • Budgets configured in angular.json and enforced in CI

Anti-patterns to flag

  • NgModule for new code; SharedModule that exports everything.
  • effect() writing to signals — use computed/linkedSignal.
  • Storing HTTP results in BehaviorSubjects and exposing .value everywhere.
  • Business logic in components; components calling HttpClient directly.
  • any in templates; strictTemplates off.
  • Constructor injection with 8 dependencies — the component is doing too much.
  • ngOnChanges implementing what computed() on inputs does.
  • *ngIf / *ngFor in new templates.