Skip to content

Angular development recipes for AI agents work when three things hold: the agent knows it is writing Angular 22 (zoneless, OnPush by default, Signal Forms stable), it can query the Angular CLI MCP server instead of guessing, and every change passes ng build, ng test on Vitest and a Playwright check.

This page is for developers who let Claude Code, Codex or Cursor write Angular code. The failure is familiar: the agent produces a component that compiles, the spec passes, and the view never updates, because the agent mutated a plain field the way Zone.js-era Angular allowed, and your app has no Zone.js to notice. Or it hands you BehaviorSubject services, *ngIf, @HostListener and a fakeAsync test that cannot run under Vitest.

  • A one-command setup that gives the agent Angular’s own best-practice file and the Angular CLI MCP server
  • Ten copy-paste prompts for current Angular: components, services, Signal Forms, interceptors, routing, NgRx SignalStore, SSR, a zoneless migration, micro-frontends and directives
  • A table of the gates that sign an Angular change off without anyone reading every line
  • The Angular-specific ways agent code breaks, and how to recover from each

Which Angular 22 defaults do agents get wrong?

Section titled “Which Angular 22 defaults do agents get wrong?”

Agents were trained on a decade of NgModule, Zone.js and decorator-era code. The current APIs and defaults below contradict most of that training data:

Agent writesWhat Angular 22 doesWrite instead
A plain class field mutated in a handler, expecting the view to refreshNew CLI apps are zoneless (the application schematic’s zoneless option defaults to true), and ChangeDetectionStrategy.OnPush is the default strategyA signal() or computed() read in the template
changeDetection: ChangeDetectionStrategy.DefaultDefault is deprecated in favour of EagerLeave it out; add Eager only with a written reason
provideExperimentalZonelessChangeDetection()Not exported by @angular/core 22.2.0provideZonelessChangeDetection(), or nothing in a new app
FormGroup plumbing for a new formSignal Forms (@angular/forms/signals: form(), [formField]) are public API since 22.0Signal Forms; typed reactive forms for existing forms
fakeAsync and tick() in specsng test runs Vitest by default, and loads zone.js/testing only when the app’s polyfills include zone.jsVitest fake timers (vi.useFakeTimers()) or await fixture.whenStable()
provideClientHydration(withIncrementalHydration())Incremental hydration is on by default; withIncrementalHydration() is deprecated since 22.0provideClientHydration(), plus withEventReplay() if you want it
@HostListener, @HostBinding, ngClass, CommonModuleAngular’s own AI instructions ban them for new codeThe host object in the decorator, class and style bindings, per-directive imports

The next section puts these rules into the agent’s context so you do not have to repeat them in every prompt.

Give the agent Angular’s own context and MCP server

Section titled “Give the agent Angular’s own context and MCP server”

The Angular CLI ships two things written for coding agents. The ai-config schematic writes Angular’s best-practice instruction file and an MCP config for your tool. The ng mcp command runs the Angular CLI MCP server, whose default tools include get_best_practices, search_documentation, list_projects, run_target (build and test targets), dev-server start, stop and wait tools, and onpush_zoneless_migration.

Generate CLAUDE.md and a project .mcp.json from the repository root:

Terminal window
npx ng generate ai-config --tool=claude-code

If a CLAUDE.md already exists, the schematic (checked in @schematics/angular 22.2.0) leaves it untouched and prints a warning, while an existing .mcp.json gets the angular-cli server merged into its mcpServers. To take Angular’s guidance into your own file, move CLAUDE.md aside, run the command, and merge the two by hand. To add only the MCP server, at project scope so the team shares it:

Terminal window
claude mcp add --scope project angular-cli -- npx -y @angular/cli mcp

Add the project facts the schematic cannot know, below the generated block:

## This repository
- Angular 22.2, standalone only, zoneless. Tests: `npx ng test --watch=false` (Vitest). Lint: `npx ng lint`.
- State: signals in components; NgRx SignalStore (`@ngrx/signals` 22) for shared feature state.
- New forms use Signal Forms. Never add `zone.js`, `fakeAsync`, `@HostListener` or `CommonModule`.
- Before you finish: `npx ng build` and `npx ng test --watch=false` must both pass. Paste their last lines.

The ng build line matters most. The production build type-checks every template, so a wrong input name or a missing import fails there, not in review. For the reasoning behind project context files, see how to make a codebase agent-ready.

The prompts are tool-agnostic. What differs is how each tool runs the verification loop.

Paste the prompt into an interactive session, where auto mode is the starting permission mode from v2.1.283 (both release channels on 2026-10-12). To run a recipe headless in a script, allow only the commands it needs:

Terminal window
claude -p "$(cat prompts/angular-product-card.txt)" \
--permission-mode acceptEdits \
--allowedTools "Bash(npx ng build *)" "Bash(npx ng test *)"

Every prompt in this section names the file, the signal APIs and the proof you expect back. The 2025 file-name style (product-card.ts, not product-card.component.ts) is the CLI default in 22.x.

What proves it: the spec drives inputs through setInput() and asserts rendered text, so it fails if the template reads a stale field. A spec that only calls component methods proves nothing about change detection.

Replace BehaviorSubject services with signals and httpResource

Section titled “Replace BehaviorSubject services with signals and httpResource”

resource() and httpResource() are public API since 22.0, and the CLI’s own guidance prefers inject() and the @Service decorator for new singletons. Keep RxJS where you have real streams; move state to signals.

Build a registration form with Signal Forms

Section titled “Build a registration form with Signal Forms”

Signal Forms replace FormGroup wiring with a signal model, a schema function and the [formField] directive. The validators you need (required, email, minLength, pattern, validate, validateAsync, validateHttp, applyWhen, hidden, disabled) are all exported from @angular/forms/signals in 22.2.0.

For an existing reactive form, do not ask for a rewrite in the same change. Ask for typed NonNullableFormBuilder fixes first, then migrate one form per pull request with its tests unchanged, so the tests become the oracle for the migration.

The concurrent-401 test is the one that catches real bugs. Agents write a refresh that works for a single request and fires three refreshes when a dashboard loads three widgets.

Lazy-load routes with guards and resolvers

Section titled “Lazy-load routes with guards and resolvers”

The last line turns “is it really lazy?” into evidence. If the admin components appear under initial chunks, the prompt failed, whatever the diff says.

Manage feature state with NgRx SignalStore

Section titled “Manage feature state with NgRx SignalStore”

ng add @angular/ssr generates a server built on AngularNodeAppEngine and server routes with a RenderMode per path. Hydration comes from provideClientHydration(), which in 22.x already turns on incremental hydration and the HttpClient transfer cache; withEventReplay() is the option you add. withIncrementalHydration() is deprecated since 22.0, and the cache means responses fetched during server rendering are reused on the client, so do not ask the agent for hand-written TransferState code for plain HttpClient calls.

Migrate a legacy app to zoneless and OnPush

Section titled “Migrate a legacy app to zoneless and OnPush”

This is the recipe where the Angular CLI MCP server earns its place: its onpush_zoneless_migration tool analyses a file for Zone.js-dependent patterns and returns a migration plan the agent then applies. Run it one feature folder at a time.

Freezing the assertions is what makes this safe to accept without reading every line: the existing tests are the specification, and the agent may not move them. See protect the test oracle for the general pattern, and legacy modernization for sequencing a larger migration.

Split a shell into micro-frontends with Native Federation

Section titled “Split a shell into micro-frontends with Native Federation”

The CLI’s default application builder is esbuild-based. @angular-architects/module-federation targets webpack, so a current CLI app that keeps the default builder uses @angular-architects/native-federation instead, which delegates to the esbuild application builder.

Before you split, check that you need independent deployment. Micro-frontends trade build simplicity for team autonomy; microservices trade-offs apply to the frontend too.

How to verify agent-written Angular code without reading every line

Section titled “How to verify agent-written Angular code without reading every line”

Each gate answers one question, and together they replace a line-by-line read. The agent runs build and tests before it stops; CI runs all of them.

GateCommandWhat it proves
Build with template type-checkingnpx ng buildTemplates bind to real inputs and outputs, imports are complete, budgets hold (angular.json → budgets)
Unit and component testsnpx ng test --watch=falseBehaviour through the DOM, on Vitest, with no Zone.js crutch
Lintnpx ng lint after npx ng add angular-eslintAngular-specific TypeScript and template rules, including the template-accessibility config
End-to-endnpx playwright testReal routing, lazy chunks, hydration and forms in a browser. See end-to-end testing with agents and Playwright MCP
AccessibilityAxe checks inside the Playwright suite, after npm i -D @axe-core/playwright (4.13.0 on 2026-09-26)WCAG AA, which Angular’s own AI instructions require. See accessibility testing

You still decide two things: that the acceptance criteria in the prompt are the right ones, and that no test assertion was weakened to get to green. A reviewer who reads the spec diff and the gate output, not the implementation diff, signs off. Bundle the outputs into the pull request as described in the evidence bundle.

SymptomCauseRecovery
The view does not update after a click or an HTTP responseA plain field mutated in a zoneless, OnPush appAsk the agent to move template state to signals. Do not add zone.js back to hide it
fakeAsync throws “zone-testing.js is needed for the fakeAsync() test helper”Zone-era spec running under Vitest in a zoneless appRewrite the spec with vi.useFakeTimers() or await fixture.whenStable()
ng build fails with “is not a known element”, “Can’t bind to … since it isn’t a known property” or “No pipe found with name”A component, pipe, or directive used through a property binding is missing from the standalone component’s importsMake the agent run ng build before it stops and fix every error; this case cannot reach a green build
An attribute directive silently does nothing, with no build errorThe directive is missing from imports, and the template uses it only as a plain attribute or an event binding such as (clickOutside), which Angular compiles as an ordinary DOM attribute or listenerRequire a spec that asserts the directive’s effect (for example, that the output fires), and check the imports array in the diff
Resolved data or route params arrive as undefined inputswithComponentInputBinding() is missing from provideRouterAdd it once in app.config.ts
ReferenceError: window is not defined during SSRBrowser API read at construction timeMove the code into afterNextRender() or a platform-guarded service
The agent installs @angular-architects/module-federation in an esbuild appWebpack-era training dataSwitch to Native Federation, or keep webpack deliberately and say so in the context file
Memory grows on long sessionssubscribe() without takeUntilDestroyed(), or an observer never disconnectedGrep the diff for subscribe( and require a destroy test for each

When the agent keeps repeating the same wrong pattern, fix the context file, not the prompt: add the rule to the repository section of CLAUDE.md or AGENTS.md, or to your Cursor Rule, so the next session starts correct.

Did this guide help you?

One tap, no sign-in.

not at all a lot