Claude Code Angular Signals CLAUDE.md Zoneless TypeScript 2026

Claude Code for Angular: CLAUDE.md Rules for Signals, Zoneless Change Detection, and Standalone Components (2026)

The Prompt Shelf ·

Angular is one of the more inconsistently covered stacks in the Claude Code CLAUDE.md ecosystem, and the templates that do exist tend to freeze the framework somewhere around 2023: NgModules alongside standalone components, zone.js assumed by default, Karma listed as the test runner. None of that matches an Angular 21/22 codebase, where zoneless change detection is the default, Karma is gone in favor of Vitest, and Signal Forms exist as an experimental API most teams haven’t decided on yet.

We checked what’s currently published against Angular’s actual 2026 defaults, cross-referenced the gallery’s own Angular signals rule set, and built a template around the decisions that change file structure and test setup — not a restatement of the style guide.

Browse more real-world CLAUDE.md and AGENTS.md examples in our gallery.

Why a Generic “Angular Best Practices” List Isn’t Enough

Angular’s compiler is stricter than most frameworks about templates, but it has nothing to say about the architectural decisions that actually vary between codebases. Left without project-specific rules, Claude Code tends to:

  • Generate a new NgModule for a feature that’s supposed to be fully standalone, because plenty of training data still shows module-based Angular
  • Assume zone.js is present and write code (or tests) that depend on automatic change detection firing after every async callback
  • Reach for @Input()/@Output() decorators on a project that has already migrated to signal-based input()/output()
  • Mix constructor injection and inject() in the same file, instead of following whichever pattern the project has standardized on
  • Write a Karma/Jasmine TestBed spec with fakeAsync/tick() on a project that migrated its test runner to Vitest
  • Reach for NgRx (actions, reducers, effects, selectors) for state that a signals-only project would model with signal() and computed()

None of this fails ng build. It compiles, it might even pass review on a fast skim, and it’s exactly the kind of drift a CLAUDE.md exists to prevent.

What Changed in Angular 21/22 That Belongs in CLAUDE.md

Angular has moved fast enough in the last two release cycles that a CLAUDE.md written even a year ago can actively mislead an agent:

  • Zoneless is the default. Angular 21 made zoneless change detection the default for new projects, and zone.js is now fully optional as of Angular 22. If your project hasn’t migrated, Claude Code needs to know it’s still zone-based — otherwise it may write code assuming automatic change detection that doesn’t fire the same way under OnPush with signals.
  • Vitest replaced Karma. New Angular projects scaffold with Vitest, not Karma/Jasmine. If your project is still on Karma, say so explicitly, since Claude Code’s default test scaffolding will otherwise assume whichever runner is more common in its training data.
  • Signal Forms are experimental, not stable. Signal-based reactive forms exist but aren’t the default the way signal(), computed(), linkedSignal(), and signal-based input()/model() are. State in the CLAUDE.md whether the project has opted in, or Claude Code may reach for an API that isn’t meant for production use yet.
  • ChangeDetectionStrategy.Default is being phased toward Eager. The naming shift matters less than the underlying point: zoneless projects default to OnPush-equivalent behavior everywhere, which changes how object mutation bugs show up (or silently don’t trigger a re-render).

If the project hasn’t adopted a given change yet, that’s a completely normal thing to pin in CLAUDE.md — the failure mode is Claude Code assuming the newest default when the codebase is a version or two behind.

A CLAUDE.md Template for Angular Projects

# CLAUDE.md — Angular Project

## Stack
- Angular {version}, {zoneless / zone.js} change detection
- Components: standalone only — no new NgModules
- State: {native signals / NgRx Signal Store} (see "State Management" below)
- Testing: {Vitest / Karma+Jasmine} + {Playwright / Cypress} for e2e
- Styling: {SCSS / Tailwind}

## Component Rules
- All new components are standalone (`standalone: true` is implicit in Angular 19+, do not add NgModule declarations).
- Use signal-based `input()` / `output()` / `model()`, not the `@Input()`/`@Output()` decorators, unless the surrounding file already uses decorators.
- Use `inject()` for dependency injection in components and services. Do not mix `inject()` and constructor injection in the same class.
- Default change detection is `OnPush`. Never mutate an object or array in place and expect a re-render — create a new reference.

## State Management
- Local/component state: `signal()` and `computed()`.
- Cross-cutting app state: {NgRx Signal Store / a hand-rolled signal-based store service} — see comparison below before adding a new store.
- Do not introduce classic NgRx (actions/reducers/effects/selectors) unless the project already uses it elsewhere.

## Templates
- Use the built-in control flow (`@if`, `@for`, `@switch`) — not `*ngIf`/`*ngFor` structural directives, unless the file already uses the legacy syntax.
- Use `@defer` for below-the-fold or non-critical content.
- Use `NgOptimizedImage` for any static image, not a raw `<img>` tag.

## Testing
- Test runner: {Vitest / Karma}. Do not scaffold `TestBed` specs assuming the other runner's APIs.
- {If zoneless}: Do not rely on automatic change detection settling between async operations in tests — call `fixture.detectChanges()` explicitly or use `await fixture.whenStable()`.
- New components require a spec covering the default render and at least one signal-driven state change.

## Commands
- Build: `ng build`
- Test: `ng test`
- Lint: `ng lint`
- Serve: `ng serve`

The zoneless/zone.js line matters more than it looks — a CLAUDE.md that assumes zoneless on a zone.js project (or vice versa) produces tests that pass locally in one setup and hang or flake in the other, because fakeAsync/tick() semantics and automatic change detection timing aren’t interchangeable.

Native Signals vs. NgRx Signal Store

The gallery’s Angular signals rule set is signals-only and doesn’t touch NgRx at all — which is a reasonable default for small-to-mid apps, but it’s not a universal answer. Both are legitimate 2026 choices, and CLAUDE.md should say which one applies before Claude Code has to guess:

Native Signals (signal()/computed())NgRx Signal Store
BoilerplateMinimal — a service with signals and methodsMore structure: store definition, withState, withMethods, withComputed
Best fitSmall-to-mid apps, feature-scoped stateLarger apps needing devtools, time-travel debugging, or a shared team convention
DevToolsNone built inNgRx DevTools integration
TestingTest the service directlyTest via the store’s public API, same as any injectable
Migration pathCan grow into a store service without much reworkAlready structured; less need to migrate later

A CLAUDE.md that just says “use signals for state” is fine until the app crosses the point where a plain signal service starts accumulating so much cross-feature coordination logic that it’s effectively reimplementing a store — at which point it’s worth naming NgRx Signal Store explicitly rather than leaving Claude Code to decide mid-task.

settings.json: Pre-Approving the Angular CLI

ng build, ng test, and ng lint run constantly during an Angular session, and prompting for each one adds friction without adding safety:

{
  "permissions": {
    "allow": [
      "Bash(ng build:*)",
      "Bash(ng test:*)",
      "Bash(ng lint:*)",
      "Bash(ng generate component:*)",
      "Bash(ng generate service:*)",
      "Bash(git status)",
      "Bash(git diff:*)"
    ],
    "deny": [
      "Bash(ng update:* --allow-dirty*)",
      "Bash(ng update:* --force*)"
    ]
  }
}

The deny entries target ng update’s --allow-dirty and --force flags specifically, not ng update itself — those flags exist to skip the CLI’s uncommitted-changes and peer-dependency safety checks, which is exactly the kind of shortcut an agent might take to get past a blocking prompt without a human noticing what got skipped.

Pair the allowlist with a PostToolUse hook that runs the linter automatically after Claude Code edits a .ts or .html file, so template/style drift gets caught before it reaches a diff:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "if [[ \"$CLAUDE_FILE_PATH\" == *.ts || \"$CLAUDE_FILE_PATH\" == *.html ]]; then npx eslint --fix \"$CLAUDE_FILE_PATH\" 2>/dev/null; fi"
          }
        ]
      }
    ]
  }
}

AGENTS.md for Nx Monorepos

Most Angular codebases past a certain size run on Nx, with multiple apps and shared libraries in one workspace — and a single flat CLAUDE.md tends to bleed app-specific conventions into libraries that are supposed to stay framework-agnostic:

# AGENTS.md

## Global Rules
- Follow all rules in CLAUDE.md.
- Run `nx affected -t lint test build` before marking any task complete — not just `ng test` on the touched project.
- Never edit generated files under `dist/` or `.nx/cache/`.

## /apps/{app-name}/
Agent scope: application shell, routing, feature composition.
- Route-level components may inject libs from `/libs/feature-*` and `/libs/ui-*`, never the reverse.
- Environment-specific config only in `environment.ts`/`environment.prod.ts` — never hardcode a base URL in a component.

## /libs/ui-*/
Agent scope: presentational component libraries.
- No `HttpClient`, no router, no direct service injection tied to a specific app. These libraries take data via `input()` and emit via `output()` only.
- Every exported component needs a spec and a Storybook story if the workspace has Storybook configured.

## /libs/feature-*/
Agent scope: feature libraries — smart components, state, API calls.
- May depend on `/libs/data-access-*` and `/libs/ui-*`. May not depend on another `/libs/feature-*`.
- New API calls go through the library's existing service pattern, not a component-level `HttpClient` call.

The ui-* libraries having zero app-specific dependencies is an Nx convention, not an Angular one — and it’s exactly the kind of boundary Claude Code has no way to infer from the code alone, since Nx’s module boundary lint rules catch violations after the fact, not before Claude Code writes the import.

Common Mistakes to Watch For

Writing zone.js-era code on a zoneless project. Manual object mutation followed by an expectation that the view updates “eventually” worked under zone.js’s automatic dirty-checking. Under zoneless OnPush, it silently doesn’t re-render, and the bug only shows up in the browser, not in ng build.

Scaffolding Karma-style tests on a Vitest project (or the reverse). TestBed.configureTestingModule works with both, but fakeAsync/tick() patterns and async test helpers aren’t drop-in compatible — copying a spec from an older Angular tutorial produces a test file that fails to even parse under the wrong runner.

Mixing @Input() decorators and signal-based input() in the same component. Both work, but a component with both patterns is harder to reason about than one that picks a convention — and it’s an easy drift to introduce one file at a time without a rule against it.

Adding a new NgRx store to a signals-only app “because that’s the standard pattern.” Without an explicit note in CLAUDE.md about which state approach the project uses, Claude Code defaults to whatever’s more heavily represented in general Angular training data — which currently still skews toward classic NgRx over Signal Store.

Assuming ng update is always safe to run. It handles most migrations correctly, but --allow-dirty and --force exist specifically to bypass checks that catch real problems — an agent hitting a blocked ng update should stop and ask, not reach for the flag that unblocks it.


Angular’s pace of change over the last two release cycles — zoneless by default, Vitest replacing Karma, Signal Forms landing as experimental — means a CLAUDE.md written for an older Angular version can actively work against the codebase instead of for it. Pin the version, state whether the project is zoneless or zone-based, name the actual test runner, and the rest of the template above covers the component, state, and Nx boundary conventions that cause the most review churn on real Angular projects.

Browse more real Angular, TypeScript, and framework-specific CLAUDE.md/AGENTS.md examples in our gallery.


FAQ

Does Claude Code work well with Angular? Yes — Claude Code handles the Angular CLI (ng build, ng test, ng generate) and TypeScript without special setup. The gap isn’t capability, it’s that Angular changed fast enough in 2026 (zoneless by default, Vitest replacing Karma, Signal Forms as an experimental API) that an outdated CLAUDE.md can describe a framework version that no longer matches the project.

Should CLAUDE.md specify zoneless or zone.js? Yes, explicitly. Angular 21+ defaults to zoneless change detection, but plenty of existing codebases haven’t migrated. The two have different implications for test timing (fakeAsync/tick() vs. fixture.whenStable()) and for whether in-place object mutation triggers a re-render, so Claude Code needs to know which one applies rather than assuming the newest default.

Should I use native signals or NgRx Signal Store? It depends on app size and whether the team wants devtools/time-travel debugging. Small-to-mid apps with feature-scoped state are usually fine with plain signal()/computed() services. Larger apps, or teams that already rely on NgRx DevTools, benefit from Signal Store’s more structured withState/withMethods/withComputed pattern. State the choice in CLAUDE.md rather than leaving Claude Code to pick per-feature.

Why does the deny list block ng update --allow-dirty and --force? Because those flags exist specifically to bypass ng update’s safety checks — uncommitted changes and peer dependency conflicts — that are there to catch real problems before a migration runs. An agent that hits a blocked update should stop and let a human decide, not reach for the flag that removes the safety check.

How is this different from the gallery’s Angular signals rule set? The gallery’s Angular signals rule is a solid signals-and-standalone-components starting point, but it predates zoneless-by-default, Vitest, and Signal Forms, and it doesn’t address Nx monorepo boundaries or the native-signals-vs-NgRx-Signal-Store decision. This guide is meant to sit on top of it for teams on a current Angular version.

Related Articles

Explore the collection

Browse all AI coding rules — CLAUDE.md, .cursorrules, AGENTS.md, and more.

Browse Rules