Opinionated Rules for building production-grade Angular 18 applications using standalone components and TypeScript 5.4.
Stop wrestling with inconsistent Angular codebases and endless refactoring cycles. These Cursor Rules deliver a battle-tested framework for building maintainable, performance-optimized Angular 18 applications that scale from MVP to enterprise.
Most Angular teams face the same productivity killers:
These rules solve these specific pain points with opinionated patterns that eliminate common pitfalls before they happen.
These Cursor Rules establish a complete development framework for Angular 18 that transforms how you build applications:
Architecture First: Feature-based project structure with strict separation of concerns Type Safety: Complete TypeScript 5.4 configuration with strict checking enabled Performance Built-In: OnPush change detection, lazy loading, and memory leak prevention Testing Integrated: Unit and E2E testing patterns that work with standalone components Production Ready: Security, error handling, and deployment configurations included
takeUntilDestroyed()Instead of debating component structure every time:
// Before: Inconsistent component patterns
@Component({
selector: 'app-user-list',
templateUrl: './user-list.component.html'
})
export class UserListComponent {
users: any[] = [];
loading = false;
// Mixed patterns, no type safety
}
// After: Standardized standalone component
@Component({
selector: 'app-user-list',
standalone: true,
imports: [AsyncPipe, NgFor, UserCardComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<app-user-card
*ngFor="let user of users$ | async; trackBy: trackByUserId"
[user]="user" />
`
})
export class UserListComponent {
private readonly userService = inject(UserService);
readonly users$ = this.userService.getUsers().pipe(
takeUntilDestroyed(),
shareReplay({ refCount: true, bufferSize: 1 })
);
trackByUserId(index: number, user: User): string {
return user.id;
}
}
Transform fragile HTTP calls into bulletproof data flows:
// Your API service with built-in error handling
@Injectable({ providedIn: 'root' })
export class ApiService {
getUsers(): Observable<Result<User[], ApiError>> {
return this.http.get<User[]>('/api/users').pipe(
map(users => ({ success: true, data: users })),
catchError(error => of({
success: false,
error: this.handleApiError(error)
})),
retry({ count: 2, delay: 1000 })
);
}
}
Standalone components simplify testing dramatically:
describe('UserListComponent', () => {
it('displays users correctly', async () => {
const mockUsers = [{ id: '1', name: 'John' }];
await render(UserListComponent, {
providers: [
{ provide: UserService, useValue: { getUsers: () => of(mockUsers) }}
]
});
expect(screen.getByText('John')).toBeInTheDocument();
});
});
# Create new Angular 18 project
ng new my-app --standalone --routing --style=scss --strict
# Install dependencies
npm install @angular/material @angular/cdk
npm install -D @testing-library/angular playwright jest
Update tsconfig.json:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"useUnknownInCatchVariables": true
}
}
src/app/
├── core/ # Singleton services
├── shared/ # Reusable components
└── features/
├── users/
│ ├── users.page.ts
│ ├── user-card.component.ts
│ ├── users.store.ts
│ └── users.routes.ts
└── dashboard/
Update main.ts:
bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes, withComponentInputBinding()),
provideHttpClient(),
provideAnimations(),
{ provide: ErrorHandler, useClass: GlobalErrorHandler }
]
});
Copy the provided rules to your .cursorrules file and start building with consistent patterns from day one.
Teams using these rules report:
Ready to transform your Angular development workflow? These rules eliminate the guesswork and establish patterns that scale from startup MVPs to enterprise applications.
The choice is simple: continue debugging inconsistent codebases or build with proven patterns that just work.
You are an expert in Angular 18, TypeScript 5.4, RxJS 7+, Angular CLI, Firebase Hosting, Jest/Karma, Playwright, and modern Web API standards.
Key Principles
- Prefer feature-based project structure (e.g. /users, /auth) over type-based structure.
- Follow the Single Responsibility Principle: one component/service = one purpose.
- Keep templates declarative; move logic to the component class or dedicated helpers.
- Use functional & reactive patterns (RxJS operators) instead of manual state mutation.
- Embrace strict type safety ("strict": true) and fail-fast linting (ESLint, Prettier).
- Write tests and docs as you code – each PR must include unit tests & JSDoc.
- Aim for tree-shakable, lazy-loaded code to minimise bundle size.
TypeScript 5.4 Rules
- Enable `"noUncheckedIndexedAccess"`, `"exactOptionalPropertyTypes"`, `"useUnknownInCatchVariables"`.
- Prefer `interface` for public contracts, `type` for unions/intersections & utility types.
- Use explicit return types for all exported functions.
- Name files in kebab-case and end with `.ts`; components end with `.component.ts`.
- Limit file length ≤ 400 LOC and function length ≤ 75 LOC; split when exceeded.
- Group members in this order: public static → public instance → protected → private.
- Place Angular decorators (`@Input`, `@Output`, `@Injectable`) at the top of the class.
Error Handling & Validation
- Catch early, return early: validate inputs at function start and throw typed errors.
- Wrap all HTTP calls in an `ApiService` that returns `Observable<Result<T, ApiError>>`.
- Use RxJS operators: `catchError`, `retry`, `finalize` – never subscribe in services.
- Provide a global `ErrorHandler` that logs to Sentry/Firestore and shows user-friendly toast messages.
- Use Angular `HttpInterceptor` to inject auth tokens and handle 401/403 globally.
Angular 18 (Standalone) Rules
- Use standalone components (`standalone: true`) – avoid `NgModule` unless integrating legacy code.
- Register app-wide providers with `bootstrapApplication(AppComponent, { providers: [...] })`.
- Combine route configuration and lazy loading:
```ts
const routes = [
{
path: 'dashboard',
loadComponent: () => import('./dashboard/dashboard.page').then(m => m.DashboardPage)
}
];
provideRouter(routes, withComponentInputBinding());
```
- Prefer `OnPush` change detection; for zoneless apps use `provideZones(false)` and `runOutsideAngular` for heavy tasks.
- Use `signal`-based state only in local components; central/global state stays in RxJS stores.
- Hydration & SSR: use `provideServerHydration()` and avoid direct DOM access inside constructors.
- CSS: adopt `:host { display: block; }` & `@layer components` with Tailwind or Angular Material v18 dynamic theming.
Testing
- Use Jest for unit tests, Playwright for E2E.
- Standalone components: test with `await render(MyComponent, { providers: [...] })` from `@testing-library/angular`.
- Achieve ≥ 90 % statement coverage; every service and pure function must have tests.
Performance & Optimisation
- Lazy load routes & components, and split feature chunks.
- Use `async` pipe everywhere – never call `subscribe()` in a component template.
- Track subscriptions in services via `takeUntilDestroyed()` helper.
- Memoise expensive selectors with `shareReplay({ refCount: true, bufferSize: 1 })`.
- Prefer Web Workers for CPU-heavy tasks; communicate via `Comlink`.
Security
- Sanitize all HTML with Angular’s DomSanitizer; never disable built-in XSS protection.
- Store secrets in environment files; never commit them.
- Implement Content Security Policy (CSP) headers and enable `strict-transport-security`.
Tooling & Build
- Use Angular CLI `browser-esbuild` builder for production builds (`ng build --configuration=production`).
- Enforce linting via `ng lint --fix` in pre-commit hooks.
- Use `esbuild-inline-images` plugin to inline assets <4 KB.
- Deploy with `ng deploy firebase` and enable zero-downtime hosting.
Documentation & Comments
- Every public symbol must have JSDoc; include usage examples.
- Keep ADR (Architecture Decision Records) in `/docs/adr`.
Folder Structure (example)
```
src/
app/
core/ # singleton services (auth, logger)
shared/ # dumb reusable components/pipes
features/
users/
users.page.ts # standalone page
user-card.component.ts
users.store.ts
users.routes.ts
```
Common Pitfalls
- Forgetting `async` pipe → memory leaks.
- Mutating `@Input` directly → use setter or immutable copies.
- Direct DOM access in SSR → wrap in `isPlatformBrowser` guard.
- Subscribing inside templates with `| subscribe` syntax (anti-pattern) – always use `async` pipe.