Tutoriel pour apprendre à bien organiser son projet Angular : Structure

Image non disponible

Aujourd'hui, dans la plupart des cas, lorsque vous commencez un projet Angular (1.x) vous pensez à une architecture MVC. Il y a beaucoup de manières de coder en Angular, mais il y en a particulièrement une qui est, selon moi, plus claire. Dans cet article, je vais vous montrer comment monter un projet modulaire, avec de bonnes pratiques, facile à maintenir et à comprendre, puis nous verrons quels en sont les avantages.

Ce tutoriel n'a pas la prétention de vous montrer la bonne voie à suivre pour coder en Angular, il y a autant de manières de développer que de développeurs.

10 commentaires Donner une note à l'article (4.5)

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Organisation des fichiers

I-A. Le pattern MVC

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
source
   /assets
      /favicon
      /fonts
      /images
   /bower_components
      /angular
      /angular-routes
   /controllers
      /home.controller.js
      /articles-list.controller.js
      /article-detail.controller.js
   /directives
      /article
         /article.directive.js
         /_article.directive.html
   /filters
      /translate.filter.js
   /partials
      /_footer.html
      /_header.html
      /_home.html
   /resources
      /locale-fr_FR.json
   /sass
      /_directives.scss
      /_fonts.scss
      /_global.scss
      /screen.scss
   /services
      /locale.service.js
      /article.service.js
   /app.config.js
   /app.module.js
   /app.routes.js
   /index.html

Voici un modèle MVC comme on les « aime » : Les contrôleurs d'un côté, les vues de l'autre, etc. Logiquement c'est ce vers quoi on va se tourner et c'est aussi la logique que j'ai adoptée à mes débuts. Le principal problème que je vois ici est que lorsque j'ai envie d'ajouter ou de modifier une nouvelle fonctionnalité dans une page, il est nécessaire que j'aille chercher le contrôleur, le service, la directive, le template, etc. Cela devient rapidement plus compliqué lorsqu'on a une application avec plus de 10 contrôleurs. AngularJS nous permet de gérer nos projets d'une manière plus simple et claire.

I-B. Une organisation orientée « components »

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
source
   /assets
      /images
      /favicon
      /fonts
   /bower_components
      /angular
      /angula-routes
   /components  // Les composants qui composent la logique de notre application (controlleurs, routes, partials...)
      /home
         /_home.html
         /_home.scss
         /home.controller.js
         /home.routes.js
      /articles
         /article-list.routes.js
         /list
            /_articles-list.html
            /_articles-list.scss
            /articles-list.controller.js
         /detail
            /_article-detail.html
            /_article-detail.scss
            /article-detail.controller.js
   /modules // Tous les composants de notre application qui sont réutilisables même en dehors de notre application
      /translate
         /locale.service.js
         /translate.module.js
         /translate.filter.js
   /resources
      /locale-fr_FR.json
   /sass
      /_fonts.scss
      /_global.scss
      /screen.scss
   /services
      /article.service.js
   /shared // Tous les composants de notre application qui sont réutilisables et propres à notre application
      /article
         /_article.directive.html
         /_article.directive.scss
         /article.directive.js
      /_footer.html
      /_head.html
      /main.controller.js
   /app.config.js
   /app.module.js
   /index.html

Vous pouvez récupérer ce projet sur git : https://github.com/SoatGroup/angular-organisation

Il est vrai que de prime abord, cette structure peut paraître compliquée, mais ce n'est qu'une question d'habitude. Il faut imaginer notre application Angular comme plusieurs petites applications MVC, cela nous permet une meilleure modularité et lisibilité (après adaptation). Nous ne classons plus nos fichiers par rapport à leur catégorie mais par rapport au sujet auquel ils appartiennent.

Ici, comme vous pouvez le voir, les composants sont individuels, par exemple si je décide de supprimer le dossier app/components/articles, cela ne posera aucun problème. Un développeur va plus facilement s'y retrouver afin de modifier ou d'ajouter une fonctionnalité dans un fichier ou dans plusieurs qui sont liés entre eux. D'un simple coup d’œil on est capable d'identifier le contenu. De plus, on se rapproche d'une organisation typique d’ Angular 2, plus simple si on veut franchir le cap.

Components

Ce qu'on appelle « component » ici va correspondre aux différentes parties de notre application (article de blog, page d'accueil, liste de résultats, etc.), il va comprendre un controller, une vue, un scss et une route. Comme dit plus haut, chaque component est individuel, en gros il ne dépend pas des autres et fonctionne comme une petite application MVC.

Dans le cas du component « articles », on peut constater qu'il y deux sous-dossiers : « list » et « detail ». En effet, dans le cas où il y a plusieurs sous-sections il est préférable de faire des sous-dossiers, toujours pour une question de lisibilité.

Modules

Le dossier « modules » va contenir tous les modules externes à l'application ou tous les modules qu'on peut réutiliser dans une autre application. Ici par exemple, j'ai développé un petit module de traduction, celui-ci pourra être utilisé à peu près n'importe où. Ce qui n'est pas du tout le cas dans notre première structure en MVC.

Ressources

Nous allons mettre ici toutes nos données statiques, par exemple les fichiers de langues.

Services

J'ai préféré mettre mes services dans un dossier à part car il m'est souvent arrivé d'utiliser certains services à plusieurs endroits, mais vous êtes libres de les mettre avec vos components s'ils leur sont propres.

Shared

Dans « shared » nous allons mettre toutes nos directives, les vues qui apparaissent à plusieurs endroits dans l'application, nos filtres, etc. Avez-vous remarqué comment sont organisées les directives ? Cela peut vous sembler proche de ce qui est fait avec les components, c'est effectivement le cas car nous sommes dans une démarche qui est de nous rapprocher d'une structure type d'AngularJS 2. (Pour information : Nous pourrions ne pas utiliser de directive, je vous laisse lire la documentation).

I-C. « Encapsulation »

Afin d'éviter les conflits de scope, variables, fonctions… et surtout pour rester modulaire, il est conseillé d'encapsuler vos composants dans une fonction anonyme :

 
Sélectionnez
1.
2.
3.
(function() {
    // Mon composant…
})();

I-D. Modules

Il faut éviter de déclarer les modules en les affectant à une variable :

 
Sélectionnez
1.
var app = angular.module('MonModule', ['AutreModule']);

Pourquoi ? Parce que dans une architecture modulaire cela n'a pas lieu d'être. De plus, souvenez-vous, nous encapsulons tous nos composants dans une fonction anonyme qui a son propre scope, du coup impossible d'accéder à la variable app partout… Et c'est tant mieux :). Donc le mieux reste :

 
Sélectionnez
1.
2.
3.
4.
angular
    .module('Blog', [
        'Translate'
    ]);

I-E. Déclaration des composants

Que ce soit pour une directive, un controller ou un service, il est recommandé de les déclarer avec une fonction nommée plutôt qu'anonyme.

Pas bon :

 
Sélectionnez
1.
2.
3.
4.
5.
angular
    .module('Blog')
    .controller('Home', function() {
        // 
    }]);

Bon :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
angular
    .module('Blog')
    .controller('HomeController', [HomeController]);

///////////////

function HomeController() {
    // 
}

C'est quand même beaucoup plus lisible, non ?

I-F. ControllerAs

Je ne vais pas vous énumérer les avantages d'utiliser le controllerAs plutôt que le $scope, beaucoup d'articles en parlent, je vais juste vous montrer une syntaxe plus claire.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
angular
    .module('Blog')
    .controller('HomeController', [HomeController]);

///////////////

function HomeController() {
    var vm = this;

    // Attributs

    vm.title = 'Bienvenue sur mon blog';
    vm.news = [];

    // Méthodes

    vm.more = more;

    ///////////////

    // Définition des méthodes

    function more(key) {
        vm.news[key].more = ! vm.news[key].more;
    }
}

Même principe pour les directives, les services, etc.

I-G. Les routes

Comme vous l'avez vu dans mon organisation, il y a un fichier routes par composant, cela permet une meilleure modularité et d'y ajouter un resolver… Prenons notre exemple d'articles, afin d'éviter les chargements asynchrones dans le controller après le chargement de la route, ou afin de gérer les erreurs avant même d'atterrir dans le controller, c'est mieux de charger les données au moment de router :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
angular
    .module('Blog')
    .config(['$routeProvider', articlesRoutes]);

///////////////

function articlesRoutes($routeProvider) {
    $routeProvider
        .when('/article/:idArticle', {
            controller: 'ArticleDetailController',
            controllerAs: 'articleVm',
            templateUrl: 'partials/_article-detail.html',
            resolve: {
                data: ['$q', 'article', getDataArticleDetai]
            }
        });

    ///////////////

    function getDataArticleDetai($q, article) {
        var defer = $q.defer();

        resolve();

        return defer.promise;

        ///////////////

        function resolve() {
            article
                .get()
                .then(fetchArticle, handeError);

            ///////////////

            function fetchMenu(data) {
                defer.resolve(data);
            }

            function handeError(data) {
                defer.reject(data);
            }
        }
    }
}

I-H. Les grosses applications

Dans le cas où votre application a du succès, elle a besoin de grosses évolutions et vous êtes en pattern MVC. Votre dossier controllers contient 28 fichiers, vous n'osez même pas compter le nombre de vues et votre fichier scss contient 2000 lignes… Pas très pratique tout ça, ça donne envie de « repartir from scratch ». Dans le cas d'une petite application comme la nôtre, une structure MVC ne pose pas de problème, mais mieux vaut s'habituer à avoir de bonnes pratiques dès le départ.

La structure que je vous ai présentée est très bien adaptée pour de grosses applications. Elle vous permet une meilleure lisibilité et maintenabilité sur vos différents composants. Ajouter ou supprimer une fonctionnalité n'a jamais été aussi simple, presque aussi simple que de supprimer ou d'ajouter un dossier.

Prenez aussi l'habitude de nommer vos fichiers de manière claire (si ce n'est pas déjà fait), par exemple pour un controller : detail.controller.js, un service : article.service.js, etc.

Dans un prochain article je parlerai de l'automatisation des taches avec Gulp pour ce type de projet, puis nous aborderons la phase de compilation, de minification et d’exécution.

II. Conclusion

Il est très important de garder un code clair et facile à maintenir, surtout lorsqu'on travaille dans une équipe. Organiser ses projets de façon modulaire apporte une grande souplesse et une meilleure clarté, c'est ce qu'Angular nous propose. De plus, il est à noter que cette architecture vous permettra à terme de migrer plus facilement vers Angular 2 qui propose une architecture orientée composant.

Cette structure, comme vu plus haut, permettra de plus facilement se retrouver dans son code et donc de le maintenir ou de le déboguer plus simplement. Votre code sera plus simple à faire évoluer, ajouter un nouveau component, un nouveau module, une nouvelle directive, etc., n'interfèrera pas avec le reste de votre application (il est encore plus simple de supprimer une fonctionnalité). En ce qui concerne les tests, vous pourrez les intégrer dans chaque composant de manière plus claire.

III. Remerciements

Nous tenons à remercier la société Soat qui nous a autorisés à publier ce tutoriel.

Nous remercions également Winjerome pour la mise au gabarit et Maxy35 pour la relecture orthographique.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2016 Nicolas GARIN. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.