Structurez le projet

Lorsque vous faites  ng new  , le CLI d'Angular génère un certain nombre de fichiers et de dossiers, et toute l'application tient dans un seul dossier. À partir de là, quand vous commencez à developper votre application, comment structurer les dossiers ? Qui va où ? Comment on appelle tout ça ?

Reconnaissez le trio infernal

Commençons par l'organisation globale des dossiers de l'application, notamment à l'intérieur du dossier app. Le pattern qui est très globalement utilisé est d'avoir les trois dossiers principaux  core  ,  features  , et  shared  .

Pour décrire la différence entre ces trois dossiers, je propose l'heuristique basée sur l'import et la réutilisation des éléments :

  • importé et utilisé dans une seule feature -> features

  • importé à un seul endroit, utilisé à un ou plusieurs endroits -> core

  • importé et utilisé à plusieurs endroits -> shared

Regardons quelques exemples.

Features

Le principe de base de ce dossier est facile à comprendre. À l'intérieur de ce dossier, on créera un dossier par feature, et chaque dossier de feature contiendra tout ce qui lui "appartient" : les components, façades, store, services, models, interfaces… parfois même des directives et pipes si elles sont spécifiques à la feature.

La vraie difficulté de ce dossier, c'est de trouver comment séparer les features d'une application, et il n'y a pas une seule bonne réponse. Voici quelques idées pour guider votre réflexion :

  • commencez par des principes larges, ce qu'on appelle les Bounded Contexts en Domain-Driven Design : dans une application e-commerce, on pourrait avoir Catalogue, Gestion de commandes, et Facturation, par exemple ; essayez le plus possible de synchroniser ces définitions sur toute la stack

  • à l'intérieur de ces dossiers de Bounded Context, une séparation par page ou par zone d'interface peut devenir nécessaire lorsque ces dossiers se remplissent trop

On verra l'organisation interne des features un peu plus bas.

Core

Dans ce dossier, on retrouve les éléments du cœur de l'application :

  • les services transverses comme des services de logging, de notification, d'ouverture de dialog, de gestion d'erreur… en gros, n'importe quel service qui n'appartient pas à une seule feature (tout ce qui est service d'authentification/autorisation peut être placé ici OU dans une feature auth indépendante)

  • les intercepteurs HTTP globaux, y compris les intercepteurs d'auth si vous n'avez pas une feature auth à part

  • les guards et resolvers globaux

  • les components de layout de l'application, tout ce qui est lié à la mise en page globale

  • les models/interfaces partagés à travers l'application

Shared

Le dossier shared, comme son nom l'indique, contient tout ce qui est partagé à travers l'application. La nuance avec le dossier core, selon moi, est que les éléments shared sont importés à différents endroits à l'intérieur de l'application :

  • les components réutilisables et éléments d'UI

  • les directives partagées

  • les pipes partagées

  • les models/interfaces liés aux éléments shared (les objets de configuration de components partagés, par exemple)

Ces listes ne sont pas exhaustives : le but de les partager ici est de vous aider à développer vos propres heuristiques et à faire vos propres choix.

Les dépendances

Cette structure ne s'applique pas qu'aux dossiers : elle définit aussi la direction des dépendances.

  • corene peut dépendre que de core— si vous vous retrouvez à avoir besoin d'un fichier qui se trouve ailleurs, il faut remettre en question votre structure

  • sharedpeut dépendre de sharedet decore— des interfaces définis dans core  , par exemple, peuvent être utilisés dans un élément shared sans problème, mais un componentsharedne doit jamais contenir un component venant d'une feature, par exemple

  • feature-A  peut dépendre defeature-A, de shared, et decore, mais pas de feature-B— si vous vous retrouvez avec des interdépendances entre features, soit il faut remettre en question vos frontières de feature, soit il faut extraire la dépendance et le placer dansshared

Voilà pour les dossiers principaux ; passons maintenant à la structure interne d'un dossier de feature.

Structurez une feature

OK, vous avez défini vos bounded contexts et vous allez commencer à travailler sur votre première feature.

Mais… comment structurer mon dossier ?

Le plus simple est de ranger les choses par leur rôle : components, directives, services, store…

Pour les components, je suggère d'avoir une structure de dossiers qui reflète la structure de l'interface. Si, par exemple, vous avez un component EmployeeList qui contient des components EmployeeListItem qui, eux, contiennent des components EmployeeAvatarComponent, EmployeeOverviewComponent, EmployeeActionsComponent, ça pourrait une structure ainsi :

Arborescence de fichiers d’un projet TypeScript montrant des composants liés à la gestion des employés, structurés en sous-dossiers comme actions, avatar et overview.
La structure des dossiers components

Notez que les dossiers ne comportent pas tous le préfixe employee, alors que les fichiers si. 

  • pour les dossiers, puisqu'on est à l'intérieur du dossier  employee-list  , le préfixe  employee  ajoute du bruit sans faciliter la lecture, alors qu'utiliser uniquement ce qui change —  avatar  ,  actions  ,  overview  etc — apporte toutes les informations nécessaires

  • pour les fichiers, il est d'usage que le nom d'un fichier qui contient une classe porte le nom de cette classe — c'est une norme, et la suivre réduit la charge cognitive

La structure finale de notre feature  employee-management  pourrait ressembler à ça :

Structure de projet montrant un dossier employee-management avec des sous-dossiers pour composants, façades, modèles, services, store et tests.
La structure des dossiers features

Dans cette configuration, j'ai choisi de placer les tests dans un dossier séparé au niveau de la racine de la feature. Ce n'est pas la seule approche possible, mais elle permet de regrouper les tests par comportement plutôt que par fichier, et elle permet aussi de stocker tout ce qui est test data, fake, harness etc au même endroit que les tests.

La nomenclature

"Il n’y a que deux choses vraiment difficiles en informatique : l’invalidation du cache et le choix des noms." — Phil Karlton

Depuis 2025, l'équipe Angular suggère de ne plus ajouter les suffixes Component, Service, Directive ou Pipe à vos classes, car ça alourdit considérablement le bruit cognitif sans vraiment ajouter de valeur. Donc, par exemple :

  • EmployeeListComponent deviendrait EmployeeList

  • EmployeeService pourrait devenir Employees, EmployeeData, EmployeeApi…

  • HighlightDirective deviendrait simplement Highlight

  • ShortenPipe deviendrait Shorten

Il en va de même pour les noms de fichiers : employee-list.component.tsdevient simplement employee-list.componentetc.

Découvrez les barrel files

En TypeScript, vous avez la possibilité d'utiliser des barrel files : des fichiers, généralement nommés index.ts  , qui ne font que des "ré-exports". L'intérêt de ces fichiers est de simplifier les imports, généralement en regroupant toutes les classes d'un dossier.

Par exemple, imaginez le dossier suivant :

Dossier models contenant quatre fichiers TypeScript définissant les modèles : employé, manager, poste et salaire.
Un Barrel File de plusieurs fichiers TypeScript

Si, dans un service, j'ai besoin de ces quatre interfaces, les imports ressembleront à ceci :

import { Employee } from '../models/employee';
import { Manager } from '../models/manager';
import { Position } from '../models/position';
import { Salary } from '../models/salary';

Cependant, si on crée, dans le dossier  models  , un fichier  index.ts  comme ceci :

export * from './employee';
export * from './manager';
export * from './position';
export * from './salary';

Les imports du service deviennent donc :

import { Employee, Manager, Position, Salary } from '../models';

Les imports sont ainsi moins verbeux et plus lisibles.

Organisez via les TypeScript paths

Vous avez certainement déjà rencontré ce genre de cas de figure :

import { AuthConfig, Permission, User } from '../../../core/auth/models';

Ici, on a bien utilisé un barrel file pour n'avoir qu'un seul import, mais cet import relatif où il faut remonter de trois niveaux pour ensuite redescendre dans  core  ajoute beaucoup de bruit et peut rendre plus difficiles les refactorisations.

Pour des dossiers aussi sollicités que  core  ou  shared  , vous pouvez utiliser l'option paths dans votre tsconfig pour générer des alias. Pour l'exemple ci-dessus, dans le  tsconfig.json  à la racine du projet, on peut ajouter l'élément suivant aux  compilerOptions  :

"paths": {
    "@core/*": ["src/app/core/*"],
}

Ce qui permet de transformer l'import problématique :

import { AuthConfig, Permission, User } from '@core/auth/models';

C'est beaucoup plus lisible ! On peut même ajouter un barrel file dans le dossier  auth  qui ré-exporte le dossier  models  :

// core/auth/index.ts

export * from './models';

L'import en est encore simplifié :

import { AuthConfig, Permission, User } from '@core/auth';

Vous pouvez faire de même pour le dossier  shared  afin que les imports dans votre application soient propres et lisibles !

À vous de jouer

Contexte

Vous êtes en plein audit d'une application Angular. L'équipe de votre client vous demande spécifiquement de questionner la structure du projet. 

Consigne

  1. Choisissez un de vos projets récents (ou, à défaut, trouvez un projet open-source sur GitHub)

  2. Avec les différentes approches et techniques proposées ici, notez les endroits où vous pensez qu'une restructuration serait bénéfique, et pourquoi.

En résumé

  • Organisez votre application aveccore,shared, etfeatures, en respectant strictement les directions de dépendances entre eux.

  • Rangez les éléments par rôle tout en reflétant la hiérarchie de l'interface utilisateur dans l'organisation des composants, et créez des sous-features dès qu'un dossier devient trop volumineux.

  • Supprimez les suffixes Component, Service, Directive des noms de classes et fichiers pour réduire le bruit cognitif

  • Utilisez des barrel files (index.ts) et des TypeScript paths (@core/*,@shared/*) pour créer des imports propres et lisibles

Cette approche de structuration fonctionne parfaitement pour des applications de taille moyenne, mais que faire lorsque votre écosystème Angular grandit ?

Ever considered an OpenClassrooms diploma?
  • Up to 100% of your training program funded
  • Flexible start date
  • Career-focused projects
  • Individual mentoring
Find the training program and funding option that suits you best