← Retour au blog
Angularstandalone-componentsNgModulesmigrationarchitecture

Standalone Components: Migrating from NgModules Without Breaking Your App

Standalone components have become the default in Angular 14+, yet many production apps remain tied to NgModules. This migration is not a mandatory overhaul overnight—it's a gradual shift you can orchestrate component by component, without disruption. The main advantage is reduced boilerplate and better tree-shakeability, but the real value emerges when you combine standalone with signals and a decoupled architecture. The migration path exists because the Angular team understood that not everyone can flip a switch at once. You have time, and you have flexibility.

Why Migrate, and When to Actually Start

Before touching code, be honest: migrating costs time and focus. You gain nothing migrating a small 3-component app that doesn't change. Conversely, if you maintain a medium to large app with 50+ components and multiple developers, the benefits become tangible. NgModules create an intermediate declaration layer that dilutes responsibility and makes sharing components between projects harder. With standalone, a component is self-contained—it declares its dependencies directly. This simplifies refactoring and testing. Start when you have consolidation time, or on a dedicated modernization branch. The decision should align with your team's capacity, not a deadline.

The Step-by-Step Approach: Start with the Leaves

The worst strategy is migrating everything at once. The right one starts with the simplest, least-dependent components. Find components that depend only on CommonModule and FormsModule, with no service specifics. These are often presentation components or basic UI widgets. Transform them to standalone in one line: add standalone: true and imports: [CommonModule, FormsModule] to their decorator. Compile, run tests, merge. Once you have a few standalone components, others gradually become compatible—Angular accepts mixing NgModules and standalone in the same tree. You have no obligation to synchronize everything at once. This psychological win matters. Your team sees progress, tests pass, confidence builds.

Take a simple component—say a button or badge. Before: declared in a SharedModule. After: self-contained. Code before:

@NgModule({
  declarations: [BadgeComponent],
  imports: [CommonModule],
  exports: [BadgeComponent]
})
export class SharedModule {}

After:

@Component({
  selector: 'app-badge',
  template: '...',
  standalone: true,
  imports: [CommonModule]
})
export class BadgeComponent {}

That's it. Code consuming BadgeComponent via SharedModule continues working unchanged.

Modules That Still Matter

Warning: don't trash all modules. Some retain real value. NgModules shine when orchestrating global providers, HTTP interceptors, or initialization logic. An initialization module configuring HttpClientModule with an auth interceptor, or bootstrapping a third-party library, keeps its purpose. You can coexist: migrate components to standalone while keeping a slim AppModule that bootstraps the app and provides centralized services. This hybrid approach is stable and recommended by the Angular team itself. Pragmatism over purity—always.

Common Pitfalls

Pitfall one: forgetting directives and pipes also need declaration. When transforming a component to standalone, you must import CommonModule *and* enumerate every custom directive or pipe it uses. If you had a SharedModule reexporting a custom directive, add it explicitly to imports . Pitfall two: services provided globally via a module. If a service was provided once in AppModule (with providedIn: 'root' ), fine. But if you had a service provided in a feature module, and that module disappears during migration, the service may not instantiate. Always check services have providedIn: 'root' or are listed in bootstrapApplication. Pitfall three, more subtle: circular dependencies. Modules could hide or circumvent them via reexports. Standalone exposes them, and the compiler rejects them. Good thing—it forces refactoring. If component A imports B and B imports A, break the cycle by extracting a shared interface or service. This is not a bug; it's a code smell detector.

Routing Strategy and Lazy-Loading

Routing changes slightly. With modules, you used loadChildren: () => import('./feature.module').then(m => m.FeatureModule) . With standalone, you import components directly: loadComponent: () => import('./feature.component').then(c => c.FeatureComponent) . Routes themselves become more decentralized—each standalone component can have children in its own definition. This makes routing modular and readable. Beware: if you use guards or resolvers, they must be provided correctly. A guard used everywhere should have providedIn: 'root' . A guard local to a feature should be provided in the component's providers or at the route level. This clarity is a feature, not a burden.

Progressive Migration: A Real Example

Imagine a medium app with an AppModule, three feature modules, and a dozen components. Step one: migrate pure presentation components (no injected services). Step two: migrate components with services (already using providedIn: 'root' ). Step three: convert feature modules to route groups with standalone components. Step four: simplify AppModule until only bootstrap and global config remain. This might take three or four weeks on a medium-sized app. The payoff? Clearer code, fewer files, better tree-shakeability (20–30% bundle gains, depending on structure), and easier onboarding for new developers. You also gain confidence—each step is small, testable, and reversible.

Conclusion: Pragmatism and Progressivity

Standalone components aren't revolutionary, but a simplification worth pursuing for medium and large apps. Migration is not urgent, but recommended. The key is not doing it all at once. Adopt a progressive cadence, test thoroughly, and watch for circular dependencies and forgotten services. Once migrated, your codebase is lighter, easier to maintain, and ready for Angular's next evolution.

Développeur Angular & Mobile freelance — Strasbourg.

© 2026 Emilien Pons — Tous droits réservés.Conçu avec Angular, PrimeNG et ❤️