Skip to content
·10 min read

CLAUDE.md for Angular: AI-Proof Your Project Setup

Angular moves fast. Standalone components, signals, and the modern stack have arrived. But AI agents still generate outdated NgModule chaos. Here's how a CLAUDE.md file keeps your project on the right track—even when Claude or ChatGPT is writing code.

The Problem: Angular's Fast Evolution Meets AI Agents

Angular has undergone a quiet revolution. Five years ago, NgModules were mandatory. Today they're optional. Observables ruled state management; now signals are the preferred choice. Template-driven forms were common; now the framework pushes reactive forms.

But here's the catch: AI agents trained on a broad swath of internet code still default to older patterns. Ask Claude to scaffold a new component, and it might generate:

  • NgModules when standalone components are the standard
  • BehaviorSubject for everything instead of signals
  • OnPush change detection forgotten entirely
  • Template-driven forms instead of reactive
  • Missing trackBy in *ngFor loops

This isn't because AI is bad at Angular. It's because AI needs guardrails. And that's what a CLAUDE.md file provides: a contract between you and any AI agent—including yourself six months from now—about what modern Angular looks like in your codebase.

Why CLAUDE.md Matters for Angular Teams

A CLAUDE.md file is a decision ledger. It documents:

  • Which Angular patterns your team uses (and which you forbid)
  • How your file structure works
  • Your code conventions and naming rules
  • Which tools you've chosen for state, testing, and build
  • Common pitfalls and how to avoid them

When Claude (or any AI) encounters these rules at the start of a task, it adjusts its output accordingly. You get:

  • Consistency: Every feature built with the same patterns
  • Faster reviews: Code reviews focus on logic, not style
  • Onboarding clarity: New devs know what "right" looks like
  • AI as a tool, not a liability: Agents generate code that fits your stack

The Modern Angular Stack

1. Standalone Components Are the Default

Forget NgModules for application code. They're still needed for third-party library setup, but your features should be built with standalone components.

// ✅ Modern Angular (standalone)
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProductListComponent } from './product-list.component';

@Component({
  selector: 'app-products',
  standalone: true,
  imports: [CommonModule, ProductListComponent],
  template: `<app-product-list [products]="products"></app-product-list>`
})
export class ProductsComponent {
  products = [{ id: 1, name: 'Widget' }];
}

// ❌ Outdated (NgModule-based)
@NgModule({
  declarations: [ProductsComponent, ProductListComponent],
  imports: [CommonModule]
})
export class ProductsModule { }

Why? Standalone components are smaller, tree-shake better, and make dependency graphs clearer. Every new component should have standalone: true.

2. Signals for State, Not Observables

Signals (Angular 16+) are the modern way to manage reactive state. They're simpler than RxJS, more performant, and work seamlessly with change detection.

// ✅ Modern: Signals + computed + effect
import { Component, signal, computed } from '@angular/core';

@Component({
  selector: 'app-counter',
  standalone: true,
  template: `
    <p>Count: {{ count() }}</p>
    <p>Doubled: {{ doubled() }}</p>
    <button (click)="increment()">+1</button>
  `
})
export class CounterComponent {
  count = signal(0);
  doubled = computed(() => this.count() * 2);

  increment() {
    this.count.update(v => v + 1);
  }
}

// ❌ Older: BehaviorSubject + async pipe
count$ = new BehaviorSubject(0);
doubled$ = this.count$.pipe(map(v => v * 2));

For complex state, use NgRx SignalStore (the new way) instead of NgRx with reducers and effects. Both work, but SignalStore aligns with modern Angular.

3. OnPush Change Detection Everywhere

OnPush change detection isn't an optimization trick—it's the standard for performant Angular. It tells Angular to check a component only when its inputs change or an event fires, not on every change in the application.

// ✅ Every component should have this
import { Component, Input, ChangeDetectionStrategy } from '@angular/core';

@Component({
  selector: 'app-product-card',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `<h3>{{ product.name }}</h3>`
})
export class ProductCardComponent {
  @Input() product!: any;
}

4. Feature-Based Structure with Lazy Routes

Organize by feature, not by type. Each feature is a folder with its own routes, components, and services.

src/app/
  features/
    products/
      routes.ts
      components/
        product-list.component.ts
        product-detail.component.ts
      services/
        product.service.ts
    orders/
      routes.ts
      components/
        order-form.component.ts
      services/
        order.service.ts
  core/
    services/
      auth.service.ts
    guards/
      auth.guard.ts

5. Reactive Forms + Typed Forms (Angular 14+)

Stop using template-driven forms. Reactive forms are more testable, more composable, and when used with typed forms, they're type-safe.

// ✅ Typed reactive forms
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { Component } from '@angular/core';

@Component({
  selector: 'app-login',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="form" (ngSubmit)="submit()">
      <input formControlName="email" />
      <input formControlName="password" type="password" />
      <button type="submit">Sign In</button>
    </form>
  `
})
export class LoginComponent {
  form = this.fb.nonNullable.group({
    email: ['', [Validators.required, Validators.email]],
    password: ['', Validators.required]
  });

  constructor(private fb: FormBuilder) {}

  submit() {
    if (this.form.valid) {
      console.log(this.form.getRawValue()); // Type-safe!
    }
  }
}

Get the free Angular CLAUDE.md template

Enterprise-grade conventions for every major stack, plus Claude Code and prompt engineering guides. No account needed.

Download free

Smart vs. Presentational Components

Keep components focused. Smart (container) components handle logic and state. Presentational (dumb) components receive data via inputs and emit events.

// ✅ Smart component (handles state and effects)
@Component({
  selector: 'app-products-container',
  standalone: true,
  imports: [ProductListComponent],
  template: `<app-product-list
    [products]="products()"
    (selectProduct)="onSelect($event)">
  </app-product-list>`
})
export class ProductsContainerComponent {
  private productService = inject(ProductService);
  products = signal<Product[]>([]);

  constructor() {
    effect(() => {
      this.productService.getAll().subscribe(
        p => this.products.set(p)
      );
    });
  }

  onSelect(product: Product) {
    // Handle selection
  }
}

// ✅ Presentational component (just displays)
@Component({
  selector: 'app-product-list',
  standalone: true,
  imports: [CommonModule],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div *ngFor="let p of products; trackBy: trackByFn">
      <span>{{ p.name }}</span>
      <button (click)="select.emit(p)">Select</button>
    </div>
  `
})
export class ProductListComponent {
  @Input() products!: Product[];
  @Output() selectProduct = new EventEmitter<Product>();

  trackByFn(index: number, item: Product) {
    return item.id;
  }
}

The Top 7 AI Mistakes to Prevent with CLAUDE.md

Document these in your CLAUDE.md to keep AI on track:

  1. Generating NgModules: All new components are standalone. NgModules only for third-party setup.
  2. Using BehaviorSubject for everything: Use signals for local state, services for shared state.
  3. Forgetting OnPush change detection: Every component has changeDetection: ChangeDetectionStrategy.OnPush.
  4. Template-driven forms: Always use reactive forms with FormBuilder.
  5. Missing trackBy in loops: All *ngFor loops must have trackBy for performance.
  6. Untyped services: Every API call should be typed. No any.
  7. Subscribing without unsubscribe: Use the async pipe or takeUntilDestroyed() to prevent memory leaks.

Sample CLAUDE.md Section for Angular

# Angular Project Guidelines

## Components
- All new components are `standalone: true`
- Every component uses `ChangeDetectionStrategy.OnPush`
- Smart (container) components manage state
- Presentational components use @Input/@Output only
- No component logic in templates; use component methods

## State Management
- Local component state: use signals (`signal()`, `computed()`)
- Shared feature state: use NgRx SignalStore
- Global app state: only in core/ services
- No BehaviorSubject in new code; use signals instead

## Reactive Forms
- All forms are reactive (FormBuilder, FormGroup)
- Use typed forms (Angular 14+): `fb.nonNullable.group()`
- Never use template-driven forms ([(ngModel)])
- Form validators in component, not template

## Performance
- Every *ngFor must have `trackBy`
- All subscriptions cleaned up: async pipe or `takeUntilDestroyed()`
- Lazy load routes by feature

## File Structure
```
src/app/
  features/
    [feature-name]/
      routes.ts
      components/
      services/
  core/
    services/
    guards/
    interceptors/
```

## Forbidden
- ❌ NgModules for feature code
- ❌ Two-way binding [(ngModel)]
- ❌ `subscribe()` without cleanup
- ❌ `OnPush` change detection omitted
- ❌ No trackBy in loops

CLAUDE.md sets the rules. Archie runs the workflow.

Persistent memory, role-based skills, and approval gates. From idea to merged PR.

View pricing

How to Enforce CLAUDE.md in Your Workflow

Having a CLAUDE.md isn't enough—you need to use it. Here's how:

  1. Include it in every task: When you ask Claude (or any AI) to build a feature, paste or reference the CLAUDE.md at the start of your prompt.
  2. Add it to code review checklists: PR reviewers use CLAUDE.md as the standard, not their personal taste.
  3. Update it as patterns evolve: When Angular releases a new recommended pattern, update CLAUDE.md and resync the team.
  4. Add ESLint rules: Complement CLAUDE.md with linting rules (e.g., enforce OnPush, forbid NgModules, require trackBy).

Bottom Line

Angular's evolution is a strength: modern, performant, typed. But that strength is only realized if your codebase stays aligned with current best practices. A CLAUDE.md file is the glue that keeps you there—especially when writing code with AI assistance.

It takes an hour to write. It saves hundreds of hours in reviews, refactors, and confusion. In a codebase touched by AI agents, CLAUDE.md isn't optional—it's the most important file you'll write.

Ready to AI-Proof Your Project?

CLAUDE.md works for every framework. Learn how to set up structured development workflows for any tech stack—and keep AI agents on the right track.