← Retour au blog
Angularstandalone-componentsNgModulesmigrationarchitecture

Standalone Components : migrer depuis les NgModules sans casser l'app

Les standalone components sont devenus la norme dans Angular 14+, pourtant beaucoup de projets en production restent accrochés aux NgModules. Cette migration n'est pas une refonte obligatoire du jour au lendemain : c'est plutôt un changement progressif qu'on peut orchestrer composant par composant, sans disruption. L'avantage principal est la réduction du boilerplate et une meilleure tree-shakeability, mais la vraie valeur se dessine surtout quand on combine standalone avec les signals et une architecture découplée.

Pourquoi migrer, et quand vraiment commencer

Avant de toucher au code, il faut être honnête : migrer coûte du temps et de la concentration. Vous ne gagnez rien à le faire sur une petite app de 3 composants qui ne bouge plus. En revanche, si vous maintenez une app moyenne à grosse, avec 50+ composants et plusieurs développeurs, les bénéfices deviennent tangibles. Les NgModules créent une couche intermédiaire de déclaration qui dilue la responsabilité et rend plus difficile le partage de composants entre projets. Avec standalone, un composant est autonome : il déclare ses dépendances directement. Cela simplifie le refactoring et les tests. Commencez quand vous avez du temps de consolidation, ou sur une nouvelle branche dédiée à la modernisation.

L'approche par étapes : commencer par les feuilles

La pire stratégie est de tout migrer à la fois. La bonne consiste à commencer par les composants les plus simples et les moins dépendants. Cherchez les composants qui ne dépendent que de CommonModule et FormsModule, sans service spécifique. Ce sont souvent les composants de présentation ou les composants UI basiques. Transformez-les en standalone en une seule ligne : ajoutez standalone: true et imports: [CommonModule, FormsModule] à leur décorateur. Compilez, lancez les tests, et mergez. Une fois que vous avez quelques composants standalone, les autres deviennent progressivement compatibles, car Angular accepte de mélanger NgModules et standalone dans le même arbre. Vous n'avez aucune obligation de synchroniser tout d'un coup.

Prenez un composant simple, par exemple un bouton ou une badge. Avant : il était déclaré dans un SharedModule. Après : il devient autonome. Code avant :

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

Après :

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

Simple. Le reste du code qui utilisait BadgeComponent via SharedModule continue à fonctionner sans changement.

Les modules qui restent utiles

Attention : ne jetez pas tous vos modules. Certains conservent une valeur réelle. Les NgModules brillent quand ils orchestrent des fournisseurs (providers) globaux, des intercepteurs HTTP, ou des configurations d'initialisation. Un module d'initialisation qui configure HttpClientModule avec un intercepteur d'authentification, ou qui initialise une librairie tierce, garde tout son sens. Vous pouvez coexister : migrér vos composants en standalone, tout en gardant un AppModule slim qui bootstrap l'app et fournit les services centralisés. Cette approche hybride est stable et recommandée par l'équipe Angular elle-même.

Les pièges courants

Le piège numéro un : oublier que les directives et pipes doivent aussi être déclarés. Quand vous transformez un composant en standalone, vous devez non seulement importer CommonModule, mais aussi énumérer chaque directive ou pipe custom qu'il utilise. Si vous aviez un SharedModule qui réexporte une directive personnalisée, il faut l'ajouter explicitement à imports . Deuxième piège : les services fournis globalement via un module. Si un service était fourni une seule fois dans AppModule (avec providedIn: 'root' ), tout va bien. Mais si vous aviez un service fourni dans un feature module, et que ce feature module disparaît lors de la migration, le service peut ne pas être instancié. Vérifiez toujours que vos services ont providedIn: 'root' ou qu'ils sont listés dans bootstrapApplication. Troisième piège, plus subtil : les dépendances circulaires. Avec les modules, on pouvait les masquer ou les contourner via des réexportations. Avec standalone, elles deviennent évidentes et le compilateur les refuse. C'est une bonne chose, mais cela signifie que vous devez refactoriser. Si composant A importe B et B importe A, il faut casser le cycle en extrayant une interface ou un service commun.

Stratégie de routing et lazy-loading

Le routing change un peu. Avec les modules, on utilisait loadChildren: () => import('./feature.module').then(m => m.FeatureModule) . Avec standalone, on importe directement les composants : loadComponent: () => import('./feature.component').then(c => c.FeatureComponent) . Les routes elles-mêmes deviennent plus décentralisées : chaque composant standalone peut avoir ses enfants en children . Cela rend le routing plus modulaire et plus lisible. Attention cependant : si vous utilisez des guards ou des resolvers, ils doivent aussi être fournis correctement. Un guard utilisé partout devrait avoir providedIn: 'root' . Un guard local à une feature devrait être fourni dans le providers du composant ou au niveau des routes.

Migration progressive : un vrai exemple

Imaginons une app moyenne avec un AppModule, trois feature modules, et une dizaine de composants. Étape 1 : migrez les composants de présentation purs (pas de services injectés). Étape 2 : migrez les composants avec services (vous les importez déjà avec providedIn: 'root' ). Étape 3 : convertissez les feature modules en groupes de routes avec composants standalone. Étape 4 : simplifiez AppModule jusqu'à ce qu'il ne reste que bootstrap et configuration globale. Cela peut prendre trois ou quatre semaines sur une app de taille moyenne. Le bénéfice ? Un code plus clair, moins de fichiers, une meilleure tree-shakeability (20-30% de gain sur les bundles, selon votre structure), et une onboarding plus facile pour les nouveaux développeurs.

Conclusion : pragmatisme et progressivité

Les standalone components ne sont pas une révolution, mais une simplification qui vaut vraiment la peine pour les apps de taille moyenne et grosse. La migration n'est pas urgente, mais elle est recommandée. L'important est de ne pas la faire d'un coup. Adoptez une cadence progressive, testez bien, et gardez un œil sur les dépendances circulaires et les services oubliés. Une fois migrée, votre codebase sera plus légère, plus facile à maintenir, et prête pour les futures évolutions d'Angular.

Développeur Angular & Mobile freelance — Strasbourg.

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