Skip to main content
rule

angular — Cursor rule for Angular code

This rule guides Cursor on Angular file structure, components, dependency injection, state, and templates. Use it when you want consistent conventions across TypeScript and HTML files.

Install into Cursor · Manually: put the files in place All methods and platforms ↓
.cursor/rules/angular.mdc
Open on GitHub

What angular does

The rule targets *.ts and *.html files. It recommends keeping one Angular concept in each file, using kebab-case for file names and PascalCase for classes. New components, directives, and pipes should be standalone; dependencies should be obtained with inject.

For local component state, it favors Angular Signals. RxJS is reserved for asynchronous streams and side effects, with toSignal and takeUntilDestroyed shown for subscription handling. Templates should use Angular’s built-in @if, @for, and @switch syntax instead of structural directives. These are coding conventions, not an automated migration.

Add .cursor/rules/angular.mdc to your project. Because the rule covers both TypeScript and HTML, it can affect existing templates too. It recommends standalone components for new features while allowing NgModules for legacy integration or library-specific patterns.

What it helps with

  • split Angular concepts into separate files
  • apply consistent file and class naming
  • create standalone components for new features
  • manage local component state with Signals
  • clean up RxJS subscriptions when components are destroyed

Example prompts for the agent

  • Review the Angular files and separate any files that combine multiple concepts.
  • Use Signals for local component state while keeping asynchronous streams in RxJS.
  • Update the template to use @if and @for instead of structural directives.

How to install angular

Manually: put the files in place

.cursor/rules/angular.mdc

Frontmatter: description, globs, alwaysApply

Step by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules

Questions about angular

How do I install angular in Cursor?

Add .cursor/rules/angular.mdc to the project. Cursor applies the rule to TypeScript and HTML files.

What does the angular rule do?

It sets conventions for Angular file structure, components, dependency injection, state, and templates. It favors Signals for local state and Angular’s built-in control-flow syntax.

Does the rule require removing NgModules?

No. It recommends standalone components for new features but allows NgModules for legacy integration or library-specific patterns.

Source text

angular.mdc · CC0-1.0
---
description: Provides definitive guidelines for modern Angular development (2025), focusing on best practices for code structure, component architecture, state management with signals, performance, and type safety.
globs: **/*.{ts,html}
---
# Angular Best Practices

This guide outlines the definitive best practices for Angular development, ensuring consistency, maintainability, and optimal performance. Adhere to these rules strictly.

## 1. Code Organization and Structure

### 1.1 One Concept Per File (SRP)

**Rule:** Each file must contain a single Angular concept (e.g., one component, one directive, one pipe, one service, one interface).

**Explanation:** This enforces the Single Responsibility Principle (SRP), improving modularity, readability, and testability. Exceptions are rare and only for tightly coupled, non-exported elements.

**❌ BAD:**
```typescript
// user.model-and-service.ts
export interface User { id: string; name: string; }

@Injectable({ providedIn: 'root' })
export class UserService {
  // ... fetches users
}
```

**✅ GOOD:**
```typescript
// user.model.ts
export interface User { id: string; name: string; }

// user.service.ts
@Injectable({ providedIn: 'root' })
export class UserService {
  // ... fetches users
}
```

### 1.2 Consistent Naming Conventions

**Rule:** Follow kebab-case for file names (`feature-name.type.ts`) and PascalCase for class names. Use standard type suffixes.

**Explanation:** Consistent naming makes files easy to locate and understand at a glance.

**❌ BAD:**
```typescript
// MyUserList.component.ts
export class UserListComp { /* ... */ }

// authservice.ts
export class AuthenticationService { /* ... */ }
```

**✅ GOOD:**
```typescript
// user-list.component.ts
export class UserListComponent { /* ... */ }

// auth.service.ts
export class AuthService { /* ... */ }
```

## 2. Component Architecture

### 2.1 Standalone Components by Default

**Rule:** Always create standalone components, directives, and pipes. Avoid NgModules for new features unless strictly necessary for legacy integration or specific library patterns.

**Explanation:** Standalone components simplify the Angular mental model, reduce boilerplate, and improve tree-shaking.

**❌ BAD:**
```typescript
// app.module.ts
@NgModule({
  declarations: [MyComponent],
  imports: [CommonModule],
  // ...
})
export class AppModule { }

// my.component.ts
@Component({
  selector: 'app-my',
  template: `<p>My Component</p>`,
  styleUrls: ['./my.component.css'],
})
export class MyComponent { }
```

**✅ GOOD:**
```typescript
// my.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common'; // Import dependencies directly

@Component({
  selector: 'app-my',
  standalone: true, // Always standalone
  imports: [CommonModule],
  template: `<p>My Component</p>`,
  styleUrls: ['./my.component.css'],
})
export class MyComponent { }
```

### 2.2 Prefer `inject` for Dependency Injection

**Rule:** Use the `inject` function for all dependency injection, especially in field initializers. Avoid constructor injection unless required for specific lifecycle hooks or inheritance patterns.

**Explanation:** `inject` offers greater flexibility, better tree-shaking, and allows injection outside of constructors.

**❌ BAD:**
```typescript
import { MyService } from './my.service';

@Component({...})
export class MyComponent {
  private myService: MyService;

  constructor(myService: MyService) {
    this.myService = myService;
  }
}
```

**✅ GOOD:**
```typescript
import { inject } from '@angular/core';
import { MyService } from './my.service';

@Component({...})
export class MyComponent {
  // Use a private readonly field for injected services
  private readonly myService = inject(MyService);

  // You can also inject in functions if needed (e.g., providers)
  // readonly myOtherService = someFactoryFn(inject(AnotherService));
}
```

## 3. State Management

### 3.1 Prioritize Signals for Component State

**Rule:** Use Angular Signals for all local component state and simple derived state. Reserve RxJS for complex asynchronous data streams, side effects, and inter-component communication.

**Explanation:** Signals provide a reactive, performant, and simpler way to manage state, optimizing change detection automatically.

**❌ BAD:**
```typescript
// Using plain properties and relying on Zone.js for change detection
@Component({...})
export class MyComponent {
  count = 0;
  items: string[] = [];

  addItem(item: string) {
    this.items.push(item); // Direct mutation, less reactive
  }
}
```

**✅ GOOD:**
```typescript
import { Component, signal, computed } from '@angular/core';

@Component({...})
export class MyComponent {
  readonly count = signal(0); // Primitive signal
  readonly items = signal<string[]>([]); // Array signal

  // Derived state with computed
  readonly hasItems = computed(() => this.items().length > 0);

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

  addItem(item: string) {
    this.items.update(currentItems => [...currentItems, item]); // Immutable update
  }
}
```

### 3.2 RxJS for Asynchronous Streams and Effects

**Rule:** Leverage RxJS for handling HTTP requests, complex event streams, and orchestrating side effects. Always manage subscriptions to prevent memory leaks.

**Explanation:** RxJS is powerful for reactive programming with asynchronous data. Use `toSignal` or `takeUntilDestroyed` for automatic unsubscription.

**❌ BAD:**
```typescript
// Manual subscription management
ngOnInit() {
  this.userService.getUsers().subscribe(users => {
    this.users = users;
  });
}

ngOnDestroy() {
  // Forgot to unsubscribe, potential memory leak
}
```

**✅ GOOD:**
```typescript
import { Component, inject, DestroyRef } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop'; // For converting RxJS to Signal
import { UserService } from './user.service';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { tap } from 'rxjs';

@Component({...})
export class MyComponent {
  private readonly userService = inject(UserService);
  private readonly destroyRef = inject(DestroyRef);

  // Convert an RxJS Observable to a Signal, automatically unsubscribes
  readonly users = toSignal(this.userService.getUsers(), { initialValue: [] });

  // For effects or more complex streams, manage with takeUntilDestroyed
  constructor() {
    this.userService.someAction$
      .pipe(
        tap(action => console.log('Action:', action)),
        takeUntilDestroyed(this.destroyRef) // Automatic unsubscription
      )
      .subscribe();
  }
}
```

## 4. Performance Considerations

### 4.1 Use Built-in Control Flow

**Rule:** Discontinue the use of structural directives (`*ngIf`, `*ngFor`, `*ngSwitch`). Always use Angular's new built-in control flow syntax.

**Explanation:** The new control flow (`@if`, `@for`, `@switch`) offers improved performance, better type checking, and a more intuitive syntax.

**❌ BAD:**
```html
<!-- Using *ngIf -->
<div *ngIf="isLoading">Loading...</div>

<!-- Using *ngFor -->
<li *ngFor="let item of items; let i = index">{{ item.name }}</li>
```

**✅ GOOD:**
```html
<!-- Using @if -->
@if (isLoading) {
  <div>Loading...</div>
} @else {
  <div>Data loaded!</div>
}

<!-- Using @for with track -->
@for (item of items; track item.id) {
  <li>{{ item.name }}</li>
} @empty {
  <p>No items found.</p>
}
```

### 4.2 Always Use `track` Key in `@for`

**Rule:** Always provide a `track` key in `@for` loops. Use a unique, primitive property of the object (e.g., `item.id`). If iterating over primitives, use the value directly.

**Explanation:** The `track` key helps Angular optimize rendering by identifying unique items, preventing unnecessary DOM re-renders and improving performance.

**❌ BAD:**
```html
<!-- Angular will re-render all items if the array reference changes -->
@for (user of users) {
  <li>{{ user.name }}</li>
}
```

**✅ GOOD:**
```html
<!-- Optimized re-rendering based on user.id -->
@for (user of users; track user.id) {
  <li>{{ user.name }}</li>
}

<!-- For primitive arrays, track the value itself -->
@for (name of names; track name) {
  <li>{{ name }}</li>
}
```

## 5. Type Safety

### 5.1 Define Interfaces for Data Structures

**Rule:** Always define TypeScript interfaces or types for all data structures, especially those received from APIs or used in models.

**Explanation:** Interfaces provide strong type checking, improve code readability, and catch errors early.

**❌ BAD:**
```typescript
// Loosely typed data
fetchUsers().subscribe((data: any) => {
  console.log(data.users[0].firstName); // Prone to runtime errors
});
```

**✅ GOOD:**
```typescript
export interface User {
  id: string;
  firstName: string;
  lastName: string;
  email: string;
}

fetchUsers().subscribe((data: User[]) => {
  console.log(data[0].firstName); // Type-safe access
});
```

### 5.2 Default to `readonly` Properties

**Rule:** Declare component properties and injected services as `readonly` where their values are not intended to be reassigned after initialization.

**Explanation:** `readonly` encourages immutability, prevents accidental reassignments, and makes state flow more predictable.

**❌ BAD:**
```typescript
@Component({...})
export class MyComponent {
  title = 'My App'; // Can be reassigned anywhere
  private myService = inject(MyService); // Can be reassigned
}
```

**✅ GOOD:**
```typescript
@Component({...})
export class MyComponent {
  readonly title = 'My App'; // Immutable after initialization
  private readonly myService = inject(MyService); // Immutable reference to service
}
```

## 6. Accessibility (A11y)

### 6.1 Leverage Angular Aria and CDK

**Rule:** Integrate Angular Aria and the Component Dev Kit (CDK) for building accessible UI components. Always provide appropriate ARIA attributes and semantic HTML.

**Explanation:** Angular's built-in tools help ensure your applications are usable by everyone, including those with disabilities.

**❌ BAD:**
```html
<!-- Custom button without proper ARIA roles -->
<div (click)="doSomething()">Click Me</div>
```

**✅ GOOD:**
```html
<!-- Semantic button with ARIA attributes for accessibility -->
<button type="button" (click)="doSomething()" aria-label="Perform action">Click Me</button>

<!-- Using CDK for accessible interactions, e.g., cdkMenu -->
<button cdkMenuTrigger>Open Menu</button>
<div cdkMenu>
  <button cdkMenuItem>Item 1</button>
</div>
```

© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc

Similar IDE rules

All IDE rules for Frontend and UI →

From the same set87

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor