Why Cursor writes bad Angular code (and how to fix it)
You open Cursor, describe the component you need, and hit tab. Thirty seconds later you have a working Angular component. There is just one problem: it uses NgModules when your project is fully standalone. Or it imports CommonModule even though you migrated to the built-in control flow six months ago. Or it reaches for BehaviorSubject when your entire codebase uses Signals.
If this sounds familiar, you are not alone. Cursor is an incredible tool, but out of the box it has no idea what version of Angular you are running, what patterns your team has agreed on, or which APIs were deprecated two major releases ago. It generates code based on statistical probability across the entire corpus of Angular code ever written, and a huge chunk of that corpus is outdated.
The result is code that compiles but does not belong in your project. Code that passes the linter but fails the code review. Code that technically works but creates maintenance debt from the moment it lands.
The NgModule problem: standalone is the default now
Angular made standalone components the default starting with version 17. When you run ng generate component today, the output is a standalone component with no NgModule in sight. This was not a minor preference change. It was a fundamental shift in how Angular applications are organized.
Cursor does not know this. When you ask it to create a component, it will often generate a full NgModule alongside it, complete with declarations, imports, and exports arrays. Sometimes it creates the component as standalone but then wraps it in a module anyway, giving you a strange hybrid that does not match either pattern cleanly.
In a mature codebase that has already migrated to standalone, this is not just unnecessary code. It is confusing code. A junior developer sees the generated module and assumes that is the pattern they should follow. Before you know it, you have new NgModules being created for features that absolutely do not need them.
The fix is straightforward. A cursor rule that says "always generate standalone components, never create NgModules for new features" eliminates this entire category of problems. Cursor stops guessing and starts following your architecture.
Deprecated APIs that keep showing up
Angular evolves fast. APIs that were standard practice two years ago are now deprecated, and APIs that were experimental are now the recommended approach. Cursor's training data does not have a clean cutoff that matches your Angular version, so it happily generates code using APIs that are on their way out.
A common example is the HttpClientModule import pattern. In older Angular versions, you imported HttpClientModule in your root module and injected HttpClient in your services. Starting with Angular 18, the recommended approach is to use provideHttpClient in your application config. Cursor will still generate the old import pattern more often than not because the old pattern appears in millions of Stack Overflow answers, blog posts, and GitHub repositories.
The same thing happens with forms. Cursor frequently generates template-driven forms using ngModel when your team has standardized on reactive forms with FormBuilder. It generates route guards as classes implementing CanActivate when functional guards have been the standard since Angular 15. It uses ViewChild with the static flag in ways that made sense in Angular 8 but are unnecessary now.
Each of these is a small thing. But small things compound. Every deprecated API that slips into your codebase is a migration you will have to do later. Cursor rules that explicitly list which APIs to use and which to avoid keep your codebase on the modern path without requiring constant vigilance during code review.
Signals vs RxJS: Cursor picks the wrong one
Angular Signals landed as a developer preview in version 16 and became stable in version 17. They represent a simpler, more predictable model for reactive state than RxJS for many common use cases. If your team has decided to use Signals for component-level state, you need Cursor to respect that decision.
Without rules, Cursor defaults to what it has seen the most of: RxJS everywhere. It will create a BehaviorSubject in a service, pipe it through a few operators, subscribe in the component, and store the result in a local variable. This is not wrong in an absolute sense, but if your project uses Signals for component state and reserves RxJS for genuinely asynchronous streams, the generated code is fighting your architecture instead of supporting it.
The inverse is also true. Some teams have decided that RxJS remains the right tool for state management across their application and are not yet adopting Signals. Cursor might generate Signal-based code for those teams because it has seen newer tutorials and documentation. Either way, without explicit guidance, Cursor is making an architectural decision that should belong to your team.
A cursor rule that specifies "use Signals for synchronous component state, use RxJS only for HTTP calls and WebSocket streams" gives Cursor the context it needs to pick the right tool for each situation.
The control flow syntax mess
Angular's built-in control flow, the at-if, at-for, and at-switch syntax, replaced the old structural directives like ngIf, ngFor, and ngSwitch in Angular 17. The new syntax is cleaner, has better type narrowing, and does not require importing CommonModule.
Cursor regularly mixes the two. You will see a template where the first conditional uses the new at-if block syntax and the second one uses the old star-ngIf directive. Sometimes it generates at-for blocks but forgets the required track expression, which causes a compilation error. Other times it wraps elements in ng-container with star-ngIf when a simple at-if block would be more readable.
This inconsistency is jarring. Templates that mix old and new control flow syntax are harder to read, harder to search through, and harder to migrate when you eventually want to drop the old syntax entirely. Cursor rules that say "use built-in control flow exclusively, always include a track expression in at-for blocks" eliminate the mixing problem completely.
Style and structure: the invisible standards
Beyond API choices, every Angular team has conventions that are not captured by a linter. How do you name your services? Do you use barrel files? Do you prefer inline templates for small components or always use separate HTML files? Do you group files by feature or by type? Where do you put your route definitions?
Cursor has opinions on all of these, but they are not your team's opinions. Without rules, it will generate a service called DataService in one prompt and UserDataService in the next. It will put a route guard in a file called auth.guard.ts in one folder and canActivate.ts in another. It will use inline styles in one component and a separate stylesheet in the next.
These inconsistencies are individually harmless but collectively exhausting. They make your codebase feel like it was written by ten different people who never talked to each other, which is essentially what happens when you use an AI assistant without constraints.
Cursor rules that define your naming conventions, file structure preferences, and component style choices bring the same consistency to AI-generated code that a style guide brings to human-written code.
The fix is one file away
Everything described above has the same root cause: Cursor does not know your project. It knows Angular in general, drawn from a massive dataset of Angular code spanning every version, every style, and every level of quality. Your project is specific. It has a version, a set of conventions, and a list of patterns that are in and out of bounds.
Cursor rules bridge that gap. A single rules file in your repository tells Cursor which Angular version you are targeting, which patterns to use, which APIs to avoid, and how to structure the code it generates. The rules are read every time Cursor processes a prompt, so the context is always present.
The difference is immediate. Components come out standalone. Templates use the modern control flow. State management follows your chosen approach. File names match your conventions. Code reviews shift from "please rewrite this using our patterns" to "looks good, ship it."
You do not have to write these rules from scratch. The Angular Cursor Rules Builder generates a complete rules file based on the Angular version and preferences you select. Pick your version, choose your patterns, and download a rules file that makes Cursor write Angular the way your team actually wants.
Stop fixing AI-generated code by hand
Generate a cursor rules file tailored to your Angular project in under a minute.
Open the builder