$ npx rulesync-cli pull✓ Wrote CLAUDE.md (2 rulesets)# Coding Standards- Always use async/await- Prefer named exports
Rule Writing

CLAUDE.md for Angular Projects

Angular's opinionated architecture is its strength — but AI ignores it. Rules for standalone components, signals, dependency injection, RxJS, and the Angular CLI.

8 min read·January 3, 2025

AI generates NgModule-based Angular — standalone components are the present

Standalone components, signals, inject(), RxJS patterns, and typed forms

Why Angular Needs Specific AI Rules

Angular is the most opinionated frontend framework — it prescribes how you structure components, manage state, handle routing, and inject dependencies. This is Angular's strength: the conventions eliminate decision fatigue and make large codebases navigable. But AI assistants ignore these conventions, generating React-style patterns, outdated NgModule-based code, and RxJS anti-patterns.

The Angular ecosystem has shifted dramatically: standalone components replaced NgModules as the default (Angular 14+), signals replaced zone.js change detection (Angular 16+), typed forms replaced untyped forms (Angular 14+), and the inject function replaced constructor injection (Angular 14+). AI training data is dominated by the old patterns.

These rules target Angular 17+ with standalone components and signals. If you're on an older version, adjust the signal and standalone rules — but keep the architectural patterns.

Rule 1: Standalone Components (No NgModules)

The rule: 'Use standalone components for everything. Never create NgModules for new code. Components, directives, and pipes are standalone by default: @Component({ standalone: true, imports: [...] }). Import dependencies directly in the component — not through a shared module. Use provideRouter, provideHttpClient, etc. in app.config.ts — not AppModule.'

For migration: 'Existing NgModules can coexist with standalone components during migration. New code is always standalone. Import NgModule-based libraries with importProvidersFrom() in app.config.ts. Gradually migrate existing modules to standalone — there's no urgency, but new code must follow the new pattern.'

AI generates NgModule-based Angular because that's been the standard for 8+ years of Angular content. Standalone components are Angular's present and future — NgModules are legacy. One rule prevents the AI from generating the legacy pattern.

  • standalone: true on every component, directive, and pipe
  • imports: [] on the component — not through shared NgModules
  • provideRouter, provideHttpClient in app.config.ts — not AppModule
  • No new NgModules — existing modules coexist during migration
  • importProvidersFrom() for NgModule-based third-party libraries
⚠️ No More NgModules

Standalone components are Angular's default since v14. AI generates NgModules from 8+ years of training data. 'standalone: true on every component, no new NgModules' — one rule prevents the legacy pattern.

Rule 2: Signals for Reactive State

The rule: 'Use signals for component state: signal() for writable state, computed() for derived state, effect() for side effects. Use input() for component inputs (replaces @Input()). Use output() for component outputs (replaces @Output()). Use model() for two-way binding. Prefer signals over RxJS for synchronous, component-local state.'

For when to use signals vs RxJS: 'Signals for: component state, form values, UI toggles, computed properties. RxJS for: HTTP responses, WebSocket streams, complex async operations, debouncing/throttling. The rule of thumb: if the value changes synchronously and is used in the template, use a signal. If it involves async operations or complex stream transformations, use RxJS.'

Angular's signals are the biggest paradigm shift since the framework's creation. AI generates zone.js-based change detection patterns (manual ChangeDetectorRef, async pipe everywhere) because signals are too new for most training data.

💡 Signals vs RxJS

Simple rule: signals for synchronous component state (toggles, form values, computed props). RxJS for async streams (HTTP, WebSocket, debouncing). Don't use RxJS for a boolean toggle, don't use signals for an HTTP stream.

Rule 3: Dependency Injection Patterns

The rule: 'Use the inject() function for dependency injection in standalone components: private http = inject(HttpClient). Never use constructor injection in new code — inject() is simpler and works with standalone components without constructor boilerplate. Use providedIn: "root" for singleton services. Use component-level providers for scoped services.'

For service design: 'Services contain business logic and API calls — components contain only template logic. Services are injectable classes decorated with @Injectable(). Keep services focused: UserService for user operations, AuthService for authentication, CartService for shopping cart. Never put HTTP calls in components — always in services.'

For testing: 'Use TestBed.configureTestingModule with standalone component imports. Mock services with jasmine.createSpyObj or jest.fn(). Use provideHttpClientTesting for HTTP testing. Use ComponentFixture for component testing with change detection.'

Rule 4: RxJS Best Practices

The rule: 'Use RxJS operators for HTTP and async streams. Use pipe() for operator composition. Always unsubscribe: use takeUntilDestroyed() (Angular 16+) or the async pipe in templates. Use switchMap for dependent HTTP requests. Use combineLatest for combining multiple streams. Use catchError for error handling in streams — never subscribe with error callbacks.'

For common patterns: 'Use shareReplay(1) for shared HTTP responses. Use distinctUntilChanged() to prevent duplicate emissions. Use debounceTime() for search inputs. Use retry() or retryWhen() for failed HTTP requests. Use EMPTY and of() for fallback values.'

For anti-patterns to avoid: 'Never nest subscribes — use switchMap, mergeMap, or concatMap. Never subscribe in a service and store the result in a property — return the Observable and let the consumer manage the subscription. Never use toPromise() — use firstValueFrom() or lastValueFrom() instead.'

  • takeUntilDestroyed() for automatic unsubscription (Angular 16+)
  • switchMap for dependent requests — combineLatest for parallel streams
  • catchError in pipe() — never error callbacks in subscribe()
  • shareReplay(1) for shared responses — distinctUntilChanged for dedup
  • Never nested subscribes — use higher-order mapping operators
  • firstValueFrom() over deprecated toPromise()
ℹ️ takeUntilDestroyed

Angular 16+ provides takeUntilDestroyed() — automatic unsubscription when the component is destroyed. No more manual Subject + takeUntil pattern. One operator replaces the most common Angular memory leak source.

Rule 5: Angular CLI and Project Structure

The rule: 'Use Angular CLI (ng generate) for creating components, services, pipes, and directives — never create files manually. Follow the Angular Style Guide: feature-based directory structure (users/, products/, auth/), barrel exports (index.ts) for public API, and consistent file naming (user.component.ts, user.service.ts, user.model.ts).'

For routing: 'Use the Angular Router with lazy-loaded standalone components: loadComponent: () => import("./pages/home.component"). Define routes in a separate routes.ts file. Use route guards (canActivate, canMatch) for auth protection. Use resolvers for pre-fetching route data.'

For forms: 'Use typed reactive forms (FormGroup, FormControl with generics) — never template-driven forms for anything beyond simple inputs. Use FormBuilder for form construction. Use Validators for built-in validation, custom validator functions for business rules. Access form values with .getRawValue() for complete type safety.'

Complete Angular Rules Template

Consolidated rules for Angular 17+ projects.

  • Standalone components: standalone: true, component-level imports, no NgModules
  • Signals: signal() for state, computed() for derived, input()/output() for I/O
  • inject() for DI — providedIn: root for singletons — services for all business logic
  • RxJS for async: pipe operators, takeUntilDestroyed, no nested subscribes
  • Typed reactive forms — FormBuilder — Validators — never template-driven for complex
  • Angular CLI for scaffolding — feature-based directories — lazy-loaded routes
  • OnPush change detection on all components — signal-based for optimal performance
  • Karma/Jest for testing — TestBed with standalone — provideHttpClientTesting