Angular standalone components: why NgModules are dead

March 2026·7 min read

If you started a new Angular project today, you would not write a single NgModule. Not because they were removed from the framework, but because they are no longer necessary. Standalone components replaced NgModules as the default way to organize Angular applications, and the shift happened faster than most teams expected.

This is not a minor quality-of-life improvement. It is a fundamental change in how Angular applications are structured. If you are still writing NgModules for new features, you are writing more boilerplate than you need to, and you are making it harder for AI code assistants to generate correct code for your project.

Here is everything you need to know about standalone components, how they work, and why your cursor rules should enforce them as the default pattern.

What standalone components actually are

A standalone component is an Angular component that manages its own dependencies directly, without needing an NgModule to declare it or provide its imports. Before standalone components existed, every component had to belong to exactly one NgModule. That module was responsible for declaring the component, importing anything it needed (like CommonModule for *ngIf, or FormsModule for ngModel), and exporting it so other modules could use it.

Standalone components skip that entire layer. They declare their imports right in the component decorator. A component that needs the Router and a shared button component simply lists them in its own imports array. No intermediary module required.

The mental model is simpler: a component is a self-contained unit. It knows what it needs, it declares what it needs, and it does not rely on some distant module file to wire things together. If you have ever opened a component and then had to hunt through three different module files to figure out why a pipe was not available, you understand the pain that standalone components eliminate.

In practical terms, a standalone component looks almost identical to a regular component. The only visible difference is theimports array in the @Component decorator. Since Angular v17, the standalone: true flag is implicit -- you do not even need to write it. Every new component is standalone by default unless you explicitly opt out.

How standalone components replaced NgModules

The transition happened in stages. Angular v14 introduced standalone components as an opt-in feature. You had to explicitly set standalone: true in the component decorator. Most teams treated it as experimental. The Angular team treated it as the future.

Angular v15 made standalone components production-ready and introduced standalone-friendly APIs for routing and bootstrapping. Instead of bootstrapping a module, you could bootstrap a single component. Instead of defining routes in a module, you could define them as a flat array and lazy-load individual components.

Angular v16 continued the trend, and by v17 the default changed. The Angular CLI began generating standalone components by default. The standalone: true property became implicit. If you generated a new component with ng generate component, it was standalone without you doing anything.

By 2026, NgModules are still supported -- Angular has always maintained strong backward compatibility -- but they are no longer the recommended architecture for new code. The Angular documentation leads with standalone components. The CLI defaults to them. Third-party libraries have updated their APIs to work with them. NgModules are a legacy pattern at this point, maintained for existing codebases but not encouraged for new work.

How imports work without modules

This is where some developers get confused. In the NgModule world, you imported entire modules, which brought along everything those modules exported. FormsModule gave you ngModel, ngForm, and a bunch of related directives. CommonModule gave you *ngIf, *ngFor, the async pipe, and more.

With standalone components, you import exactly what you need. If your component uses RouterLink, you importRouterLink. If it uses a custom pipe, you import that pipe. The imports are granular and explicit.

This feels like more work at first. Instead of one blanketCommonModule import, you might need to importNgIf, NgFor, and AsyncPipeindividually. In practice, though, the built-in control flow syntax (@if, @for, @switch) introduced in Angular v17 makes most of those imports unnecessary. You do not import anything to use @if. It is built into the template compiler.

The result is that a typical standalone component has a short, focused imports list. It imports the specific components, directives, and pipes it actually renders in its template. Nothing more, nothing less. Your dependency graph becomes explicit and easy to trace, which is a significant improvement for maintainability.

Lazy loading without NgModules

One of the strongest arguments for NgModules used to be lazy loading. You would create a feature module, define its routes, and use loadChildren to lazy-load the entire module when a user navigated to that section of the app. The module boundary was also the code-splitting boundary.

Standalone components handle this differently and arguably better. Instead of lazy-loading an entire module, you lazy-load individual components using loadComponent in your route configuration. Each route can point directly to a component file.

This gives you finer-grained code splitting. Instead of loading an entire feature module when a user visits a section, you load exactly the component they need. If a feature has ten screens, the user only downloads the one they navigate to, not the shared module that wraps all ten.

For large applications, this can make a meaningful difference in initial load times and navigation performance. It also makes the route configuration easier to read. Each route is a direct mapping from a path to a component, without the indirection of module imports and child route configurations nested inside module files.

Migrating from a module-based architecture

If you have an existing Angular application that uses NgModules, the migration path is incremental. You do not need to convert everything at once. Standalone components and module-based components can coexist in the same application.

The Angular team provides a migration schematic:ng generate @angular/core:standalone. It automates the most tedious parts of the conversion. It will convert your components to standalone, move imports from modules into component decorators, and clean up the now-unnecessary module files.

The recommended migration strategy is to start with leaf components -- components that do not have children and are used in multiple places. Convert those to standalone first. Then work your way up through shared components, feature components, and finally the root module itself.

The trickiest part of migration is usually the bootstrapping. If your application bootstraps an AppModule, you need to switch to bootstrapping an AppComponent with bootstrapApplicationand move your providers (HttpClient, Router, etc.) into aprovideRouter / provideHttpClient call. The schematic handles this, but it is worth understanding what changes so you can verify the result.

Most teams report that the migration takes a few days for a medium-sized application and that the resulting codebase is noticeably simpler. Fewer files, fewer indirection layers, and a clearer dependency graph.

Why cursor rules should enforce standalone-first patterns

Here is where this connects to AI-assisted development. When you ask Cursor or Copilot to generate an Angular component, the AI needs to decide: should it create a standalone component or a module-based component? Should it import CommonModule or use the new control flow? Should it create a separate module file?

Without explicit guidance, AI assistants often generate module-based code. Their training data includes years of Angular tutorials and Stack Overflow answers that predate standalone components. The patterns they fall back on are frequently outdated.

A cursor rules file solves this. By adding a rule that says "always generate standalone components" and "never create NgModules for new features," you eliminate an entire category of AI mistakes. The assistant knows the architecture your team has chosen and generates code that fits.

Good cursor rules for standalone components go beyond just the standalone flag. They specify that imports should be granular (not entire modules), that lazy loading should useloadComponent instead of loadChildrenwith modules, and that providers should use the functional API (provideRouter, provideHttpClient) instead of module-based provider arrays.

When your rules are specific, the generated code is consistent. Every component the AI creates follows the same structure. Every route definition uses the same pattern. Your codebase stays coherent even as multiple developers and AI assistants contribute to it simultaneously.

The bottom line

Standalone components are not a new feature anymore. They are the standard. Angular has been moving toward this architecture for three major versions, and in 2026 there is no reason to start a new feature with NgModules.

If you are building cursor rules for your Angular project, making standalone-first the default is one of the highest-impact decisions you can make. It reduces the amount of generated boilerplate, eliminates a common source of AI confusion, and keeps your codebase aligned with where Angular is heading.

The migration from NgModules is straightforward. The tooling supports it. The framework defaults to it. The only thing left is to make sure your team -- and your AI assistants -- are on the same page.

Build your rules file

Configure standalone-first patterns and other Angular conventions in a single rules file your AI assistant will follow.

Open the builder