---
title: "Standalone Without Turning NgModules Into a Villain"
description: "A practical rule for using standalone components, route providers, and remaining NgModules in mature Angular applications."
deck: "Standalone changes the architectural default, but it does not make every existing NgModule an emergency. Components are [standalone by default since Angular v17](https://angular.dev/guide/components) and NgModule has been optional since v19. The [official standalone migration docs](https://angular.dev/reference/migrations/standalone) are explicit: existing apps can adopt incrementally without breaking changes."
author: "André Ramos"
url: "https://andreramos.dev/angular/standalone-without-ngmodule-dogma/"
lang: "en"
type: "article"
---

# Standalone Without Turning NgModules Into a Villain

> Standalone changes the architectural default, but it does not make every existing NgModule an emergency. Components are [standalone by default since Angular v17](https://angular.dev/guide/components) and NgModule has been optional since v19. The [official standalone migration docs](https://angular.dev/reference/migrations/standalone) are explicit: existing apps can adopt incrementally without breaking changes.

## The better question

In a mature Angular app, the standalone conversation should not start with 'how do we remove every module?'. Start with a smaller question: where does this feature's boundary live today, and would standalone make that boundary easier to read, load, and test?

Angular components are standalone by default in current Angular. That changes the default for new code. It does not mean a stable internal library, a third-party integration, or a legacy feature module must be rewritten before the next release.

I would keep a stable `LegacyPaymentsModule` if it wraps a third-party SDK, exposes a known public API, and is not blocking lazy loading, ownership, or tests. That is not nostalgia; it is scope control.

## The decision table

| Situation | Default move | Evidence to require |
|---|---|---|
| New route or feature | Use standalone components and route-level providers | Imports are explicit and the route can own the feature facade. |
| Module that only exists for declarations | Migrate when the area is touched | Build and feature smoke test pass without broad import churn. |
| SharedModule with everything | Split by real usage | A dependency list shows which imports each feature actually needs. |
| Stable library module | Keep it unless there is a release reason | The module still expresses a public API or third-party contract. |
| Third-party SDK wrapper | Keep or isolate behind a route | The migration would add risk without improving ownership. |
| Root providers everywhere | Move feature state closer to the route | Navigation, reuse, and teardown behavior are understood. |

## A route is a better boundary than a slogan

The useful standalone migration is often a route migration. A route can lazy-load a component, scope providers, and become the place where the team sees the feature boundary without opening a module file first.

If every provider stays at root and every component imports a giant shared bucket, the code is not meaningfully more modular. It only removed a layer of ceremony.

*Route-owned feature boundary*

```ts
export const routes: Routes = [
  {
    path: 'billing',
    providers: [BillingFacade],
    loadComponent: () =>
      import('./billing/billing-page.component')
        .then(m => m.BillingPageComponent),
  },
];
```

## What to avoid

Avoid a migration branch whose only promise is 'remove NgModules'. That kind of branch tends to touch too many files, create review fatigue, and produce little product value.

The better move is narrower: use standalone as the default for new code, migrate modules that block lazy loading or hide dependencies, and leave stable modules alone until the team has a reason to touch them.

## Reusable artifact: Standalone adoption rule

- Use standalone by default in new Angular code.
- Use routes as feature boundaries, not SharedModule as the first design tool.
- Keep NgModules when they still express a stable integration boundary.
- Migrate touched modules only when the diff improves ownership, loading, or reviewability.

## Sources checked

- https://angular.dev/guide/components
- https://angular.dev/reference/migrations/standalone
- https://angular.dev/guide/di/dependency-injection-providers
- https://angular.dev/api/router/Route
- https://angular.dev/api/core/inject
- https://blog.angular.dev/announcing-angular-v21-57946c34f14b

## Related

- [Modern Angular for Production Teams](https://andreramos.dev/angular/modern-angular-production-teams/)
- [Facade Pattern in Angular: When Components Know Too Much](https://andreramos.dev/angular/facade-pattern-in-angular/)
- [Adapter Pattern in Angular: Isolating APIs and Browser-Only Libraries](https://andreramos.dev/angular/adapter-pattern-in-angular/)
